Img32.Resamplers.pas 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863
  1. unit Img32.Resamplers;
  2. (*******************************************************************************
  3. * Author : Angus Johnson *
  4. * Version : 4.8 *
  5. * Date : 10 January 2025 *
  6. * Website : http://www.angusj.com *
  7. * Copyright : Angus Johnson 2019-2025 *
  8. * Purpose : For image transformations (scaling, rotating etc.) *
  9. * License : http://www.boost.org/LICENSE_1_0.txt *
  10. *******************************************************************************)
  11. interface
  12. {$I Img32.inc}
  13. uses
  14. SysUtils, Classes, Math, Img32;
  15. // Premultiplies the alpha channel into the color channels from pSrc and stores
  16. // it into pDst. pSrc and pDst can be the same pointer.
  17. procedure PremultiplyAlpha(pSrc, pDst: PARGB; count: nativeint); overload;
  18. // BoxDownSampling: As the name implies, is only intended for image
  19. // down-sampling (ie shrinking images) where it performs a little better
  20. // than other resamplers which tend toward pixelation. Nevertheless, this
  21. // routine is inferior to other resamplers when performing other
  22. // types of transformations (ie when enlarging, rotating, and skewing images),
  23. // so BoxDownSampling should not be used as a general purpose resampler.
  24. procedure BoxDownSampling(Image: TImage32; scale: double); overload;
  25. procedure BoxDownSampling(Image: TImage32; scaleX, scaleY: double); overload;
  26. procedure BoxDownSampling(Image: TImage32; newWidth, newHeight: Integer); overload;
  27. procedure BoxDownSampling(Image, TargetImage: TImage32; scale: double); overload;
  28. procedure BoxDownSampling(Image, TargetImage: TImage32; scaleX, scaleY: double); overload;
  29. procedure BoxDownSampling(Image, TargetImage: TImage32; newWidth, newHeight: Integer); overload;
  30. procedure NearestNeighborResize(Image: TImage32; newWidth, newHeight: Integer); overload;
  31. procedure NearestNeighborResize(Image, TargetImage: TImage32; newWidth, newHeight: Integer); overload;
  32. procedure ResamplerResize(Image: TImage32; newWidth, newHeight: Integer); overload;
  33. procedure ResamplerResize(Image, TargetImage: TImage32; newWidth, newHeight: Integer); overload;
  34. // The following general purpose resamplers are registered below:
  35. // function NearestResampler(img: TImage32; x, y: double): TColor32;
  36. // function BilinearResample(img: TImage32; x, y: double): TColor32;
  37. // function BicubicResample (img: TImage32; x, y: double): TColor32;
  38. // function WeightedBilinear(img: TImage32; x, y: double): TColor32;
  39. implementation
  40. uses
  41. Img32.Transform;
  42. var
  43. sinWeighted: array [0..255] of Cardinal;
  44. //------------------------------------------------------------------------------
  45. // NearestNeighbor resampler
  46. //------------------------------------------------------------------------------
  47. function NearestResampler(img: TImage32; x, y: double): TColor32;
  48. var
  49. xi, yi: integer;
  50. begin
  51. xi := Round(x); yi := Round(y);
  52. if (xi < 0) or (yi < 0) or (xi >= img.Width) or (yi >= img.Height) then
  53. Result := clNone32 else
  54. Result := img.Pixels[xi + yi * img.Width];
  55. end;
  56. //------------------------------------------------------------------------------
  57. // BiLinear resampler
  58. //------------------------------------------------------------------------------
  59. function BilinearResample(img: TImage32; x, y: double): TColor32;
  60. var
  61. iw, ih: integer;
  62. xx, yy, xR, yB: integer;
  63. weight: integer;
  64. pixels: TArrayOfColor32;
  65. weightedColor: TWeightedColor;
  66. xf, yf: double;
  67. begin
  68. iw := img.Width;
  69. ih := img.Height;
  70. pixels := img.Pixels;
  71. if (x < 0) then
  72. begin
  73. if (x < -0.5) then
  74. begin
  75. xf := -x;
  76. end else
  77. begin
  78. x := 0;
  79. xf := 0;
  80. end;
  81. xx := 0;
  82. xR := 0;
  83. end else
  84. begin
  85. xf := 1-frac(x);
  86. if x >= iw -1 then
  87. begin
  88. xx := iw -1;
  89. xR := xx;
  90. end else
  91. begin
  92. xx := Trunc(x);
  93. xR := xx +1;
  94. end;
  95. end;
  96. if (y < 0) then
  97. begin
  98. if (y < -0.5) then
  99. begin
  100. yf := -y;
  101. end else
  102. begin
  103. y := 0;
  104. yf := 0;
  105. end;
  106. yy := 0;
  107. yB := 0;
  108. end else
  109. begin
  110. yf := 1-frac(y);
  111. if y >= ih -1 then
  112. begin
  113. yy := ih -1;
  114. yB := yy;
  115. end else
  116. begin
  117. yy := Trunc(y);
  118. yB := yy +1;
  119. end;
  120. end;
  121. weightedColor.Reset;
  122. weight := Round(xf * yf * 255); //top-left
  123. if weight > 0 then
  124. begin
  125. if (x < 0) or (y < 0) then
  126. weightedColor.AddWeight(weight) else
  127. weightedColor.Add(pixels[xx + yy * iw], weight);
  128. end;
  129. weight := Round((1-xf) * yf * 255); //top-right
  130. if weight > 0 then
  131. begin
  132. if (x > iw - 0.5) or (y < 0) then
  133. weightedColor.AddWeight(weight) else
  134. weightedColor.Add(pixels[xR + yy * iw], weight);
  135. end;
  136. weight := Round(xf * (1-yf) * 255); //bottom-left
  137. if weight > 0 then
  138. begin
  139. if (x < 0) or (y > ih - 0.5) then
  140. weightedColor.AddWeight(weight) else
  141. weightedColor.Add(pixels[xx + yB * iw], weight);
  142. end;
  143. weight := Round((1-xf) * (1-yf) * 255); //bottom-right
  144. if weight > 0 then
  145. begin
  146. if (x > iw - 0.5) or (y > ih - 0.5) then
  147. weightedColor.AddWeight(weight) else
  148. weightedColor.Add(pixels[xR + yB * iw], weight);
  149. end;
  150. Result := weightedColor.Color;
  151. end;
  152. //------------------------------------------------------------------------------
  153. // WeightedBilinearResample: A modified bilinear resampler that's
  154. // less blurry but also a little more pixelated.
  155. function WeightedBilinearResample(img: TImage32; x, y: double): TColor32;
  156. var
  157. iw, ih: integer;
  158. xx, yy, xR, yB: integer;
  159. weight: integer;
  160. pixels: TArrayOfColor32;
  161. weightedColor: TWeightedColor;
  162. xf, yf: double;
  163. begin
  164. iw := img.Width;
  165. ih := img.Height;
  166. pixels := img.Pixels;
  167. if (x < 0) then
  168. begin
  169. if (x < -0.5) then
  170. begin
  171. xf := -x;
  172. end else
  173. begin
  174. x := 0;
  175. xf := 0;
  176. end;
  177. xx := 0;
  178. xR := 0;
  179. end else
  180. begin
  181. xf := 1-frac(x);
  182. if x >= iw -1 then
  183. begin
  184. xx := iw -1;
  185. xR := xx;
  186. end else
  187. begin
  188. xx := Trunc(x);
  189. xR := xx +1;
  190. end;
  191. end;
  192. if (y < 0) then
  193. begin
  194. if (y < -0.5) then
  195. begin
  196. yf := -y;
  197. end else
  198. begin
  199. y := 0;
  200. yf := 0;
  201. end;
  202. yy := 0;
  203. yB := 0;
  204. end else
  205. begin
  206. yf := 1-frac(y);
  207. if y >= ih -1 then
  208. begin
  209. yy := ih -1;
  210. yB := yy;
  211. end else
  212. begin
  213. yy := Trunc(y);
  214. yB := yy +1;
  215. end;
  216. end;
  217. weightedColor.Reset;
  218. weight := sinWeighted[Round(xf * yf * 255)]; //top-left
  219. if weight > 0 then
  220. begin
  221. if (x < 0) or (y < 0) then
  222. weightedColor.AddWeight(weight) else
  223. weightedColor.Add(pixels[xx + yy * iw], weight);
  224. end;
  225. weight := sinWeighted[Round((1-xf) * yf * 255)]; //top-right
  226. if weight > 0 then
  227. begin
  228. if (x > iw - 0.5) or (y < 0) then
  229. weightedColor.AddWeight(weight) else
  230. weightedColor.Add(pixels[xR + yy * iw], weight);
  231. end;
  232. weight := sinWeighted[Round(xf * (1-yf) * 255)]; //bottom-left
  233. if weight > 0 then
  234. begin
  235. if (x < 0) or (y > ih - 0.5) then
  236. weightedColor.AddWeight(weight) else
  237. weightedColor.Add(pixels[xx + yB * iw], weight);
  238. end;
  239. weight := sinWeighted[Round((1-xf) * (1-yf) * 255)]; //bottom-right
  240. if weight > 0 then
  241. begin
  242. if (x > iw - 0.5) or (y > ih - 0.5) then
  243. weightedColor.AddWeight(weight) else
  244. weightedColor.Add(pixels[xR + yB * iw], weight);
  245. end;
  246. Result := weightedColor.Color;
  247. end;
  248. //------------------------------------------------------------------------------
  249. // BiCubic resampler
  250. //------------------------------------------------------------------------------
  251. type
  252. TBiCubicEdgeAdjust = (eaCenterFill,
  253. eaPreStart, eaStart, eaPostStart, eaEnd, eaPostEnd);
  254. var
  255. byteFrac: array [0..255] of double;
  256. byteFracSq: array [0..255] of double;
  257. byteFracCubed: array [0..255] of double;
  258. //------------------------------------------------------------------------------
  259. function CubicInterpolate(aclr: PColor32;
  260. t: Byte; bce: TBiCubicEdgeAdjust): TColor32;
  261. var
  262. a,b,c,d: PARGB;
  263. q: TARGB;
  264. aa, bb, m0, m1: double;
  265. t1, t2, t3: double;
  266. res: TARGB absolute Result;
  267. const
  268. clTrans: TColor32 = clNone32;
  269. begin
  270. case bce of
  271. eaPreStart:
  272. begin
  273. a := @clTrans;
  274. b := @clTrans;
  275. c := PARGB(aclr);
  276. d := c;
  277. end;
  278. eaStart:
  279. begin
  280. Result := aclr^;
  281. Exit;
  282. end;
  283. eaPostStart:
  284. begin
  285. a := PARGB(aclr);
  286. b := a;
  287. Inc(aclr);
  288. c := PARGB(aclr);
  289. d := c;
  290. end;
  291. eaEnd:
  292. begin
  293. Inc(aclr);
  294. Result := aclr^;
  295. Exit;
  296. end;
  297. eaPostEnd:
  298. begin
  299. Inc(aclr);
  300. a := PARGB(aclr);
  301. b := a;
  302. c := @clTrans;
  303. d := @clTrans;
  304. end;
  305. else
  306. begin
  307. a := PARGB(aclr);
  308. Inc(aclr);
  309. b := PARGB(aclr);
  310. Inc(aclr);
  311. c := PARGB(aclr);
  312. Inc(aclr);
  313. d := PARGB(aclr);
  314. end;
  315. end;
  316. if (b.A = 0) and (c.A = 0) then
  317. begin
  318. result := clNone32;
  319. Exit;
  320. end
  321. else if (b = c) then
  322. begin
  323. result := b.Color;
  324. Exit;
  325. end
  326. else if b.A = 0 then
  327. begin
  328. // ignore differences between b & c's color channels
  329. q := c^;
  330. q.A := 0;
  331. b := @q;
  332. end;
  333. if c.A = 0 then
  334. begin
  335. // ignore differences between b & c's color channels
  336. q := b^;
  337. q.A := 0;
  338. c := @q;
  339. end;
  340. t1 := byteFrac[t];
  341. t2 := byteFracSq[t];
  342. t3 := byteFracCubed[t];
  343. // find piecewise bicubic interpolation between pixel_b and pixel_c
  344. // at point 't' (as byte div 255) ...
  345. // given parametric equation aa(t^3) + bb(t^2) + cc(t)+ dd = 0
  346. // where t(0) = pixel_b and t(1) = pixel_c
  347. // let m1 = slope at pixel_b (using slope of pixel_c - pixel_a)
  348. // let m2 = slope at pixel_c (using slope of pixel_d - pixel_b)
  349. // then t(0) = aa(0^3) + bb(0^2) + cc(0) + dd = dd
  350. // then t(1) = aa(1^3) + bb(1^2) + cc(1) + dd = aa + bb + cc + dd
  351. // differentiating parametic equation at t'(0) and t'(1) ...
  352. // t'(0) = m0 = 3*aa(0^2) + 2*bb(0) + cc = cc
  353. // t'(1) = m1 = 3*aa(1^2) + 2*bb(1) + cc = 3*aa + 2*bb + cc
  354. // t(0) = dd ::EQ1
  355. // t(1) = aa+bb+cc+dd ::EQ2
  356. // t'(0) = cc ::EQ3
  357. // t'(1) = 3*aa + 2*bb + cc ::EQ4
  358. // solving simultaneous equations
  359. // aa = 2*t(0) -2*t(1) +t'(0) +t'(1)
  360. // bb = 3*t(1) -3*t(0) -2*t'(0) -t'(1)
  361. // cc = m0
  362. // dd = t(0)
  363. m0 {aka t'(0)} := (c.A - a.A) /2;
  364. m1 {aka t'(1)} := (d.A - b.A) /2;
  365. aa := 2*b.A - 2*c.A + m0 + m1;
  366. bb := 3*c.A -3*b.A -2*m0 - m1;
  367. Res.A := ClampByte(aa*t3 + bb*t2 + m0*t1 + b.A);
  368. m0 := (c.R - a.R) /2;
  369. m1 := (d.R - b.R) /2;
  370. aa := 2*b.R - 2*c.R + m0 + m1;
  371. bb := 3*c.R -3*b.R -2*m0 - m1;
  372. Res.R := ClampByte(aa*t3 + bb*t2 + m0*t1 + b.R);
  373. m0 := (c.G - a.G) /2;
  374. m1 := (d.G - b.G) /2;
  375. aa := 2*b.G - 2*c.G + m0 + m1;
  376. bb := 3*c.G -3*b.G -2*m0 - m1;
  377. Res.G := ClampByte(aa*t3 + bb*t2 + m0*t1 + b.G);
  378. m0 := (c.B - a.B) /2;
  379. m1 := (d.B - b.B) /2;
  380. aa := 2*b.B - 2*c.B + m0 + m1;
  381. bb := 3*c.B -3*b.B -2*m0 - m1;
  382. Res.B := ClampByte(aa*t3 + bb*t2 + m0*t1 + b.B);
  383. end;
  384. //------------------------------------------------------------------------------
  385. function BicubicResample(img: TImage32; x, y: double): TColor32;
  386. var
  387. i, pi, iw, ih, last: Integer;
  388. c: array[0..3] of TColor32;
  389. xFrac, yFrac: byte;
  390. bceX, bceY: TBiCubicEdgeAdjust;
  391. begin
  392. iw := img.Width;
  393. ih := img.Height;
  394. last := iw * ih -1;
  395. if x < 1 then
  396. begin
  397. if x < -0.5 then
  398. begin
  399. xFrac := Round((1+x) *255);
  400. bceX := eaPreStart;
  401. end
  402. else if (x < 0) or
  403. ((iw = 1) and (x < 0.5)) then
  404. begin
  405. x := 0;
  406. xFrac := 0;
  407. bceX := eaStart;
  408. end
  409. else if (iw = 1) and (x > 0.5) then
  410. begin
  411. // the following is a workaround to avoid the increment in eaPostEnd
  412. bceX := eaPreStart; // ie anti-aliase but without increment
  413. xFrac := Round((1-x) *127); // reversed because 'end' not 'start'
  414. end else
  415. begin
  416. xFrac := Round(frac(x) *255);
  417. bceX := eaPostStart;
  418. end;
  419. end else
  420. begin
  421. xFrac := Round(frac(x) *255);
  422. if x > iw - 1 then
  423. begin
  424. if x > iw - 0.5 then bceX := eaPostEnd
  425. else bceX := eaEnd
  426. end
  427. else
  428. bceX := eaCenterFill;
  429. end;
  430. if y < 1 then
  431. begin
  432. if y < -0.5 then
  433. begin
  434. yFrac := Round((1+y) *255);
  435. bceY := eaPreStart;
  436. end
  437. else if (y < 0) or
  438. ((ih = 1) and (y < 0.5)) then
  439. begin
  440. y := 0;
  441. yFrac := 0;
  442. bceY := eaStart;
  443. end
  444. else if (ih = 1) and (y > 0.5) then
  445. begin
  446. // the following is a workaround to avoid the increment in eaPostEnd
  447. bceY := eaPreStart; // ie anti-aliase but without increment
  448. yFrac := Round((1-y) *127); // reversed because 'end' not 'start'
  449. end else
  450. begin
  451. yFrac := Round(frac(y) *255);
  452. bceY := eaPostStart;
  453. end;
  454. end else
  455. begin
  456. yFrac := Round(frac(y) *255);
  457. if y > ih - 1 then
  458. begin
  459. if y > ih - 0.5 then bceY := eaPostEnd
  460. else bceY := eaEnd
  461. end
  462. else
  463. bceY := eaCenterFill;
  464. end;
  465. x := Max(0, Min(iw -1, x -1));
  466. y := Max(0, Min(ih -1, y -1));
  467. pi := Trunc(y) * iw + Trunc(x);
  468. for i := 0 to 3 do
  469. begin
  470. c[i] := CubicInterpolate(@img.Pixels[pi], xFrac, bceX);
  471. inc(pi, iw);
  472. if pi > last then break;
  473. end;
  474. Result := CubicInterpolate(@c[0], yFrac, bceY);
  475. end;
  476. //------------------------------------------------------------------------------
  477. //------------------------------------------------------------------------------
  478. {$RANGECHECKS OFF} // negative index usage for Delphi 7-2007
  479. procedure PremultiplyAlpha(pSrc, pDst: PARGB; count: nativeint);
  480. var
  481. a: byte;
  482. tab: PByteArray;
  483. c: TColor32;
  484. s, d: PColor32Array;
  485. begin
  486. if count = 0 then exit;
  487. // Use negative index trick
  488. inc(pSrc, count);
  489. inc(pDst, count);
  490. count := -count;
  491. // This function is optmized with the assumption that if a pixel has a certain
  492. // alpha channel, then the propability that the following pixels have the same
  493. // alpha channel, is very high.
  494. c := PColor32Array(pSrc)[count];
  495. a := c shr 24;
  496. while True do
  497. begin
  498. case a of
  499. 0: // Special handling for 0 => color becomes black
  500. begin
  501. // Win32: Load stack variable into CPU register
  502. s := PColor32Array(pSrc);
  503. d := PColor32Array(pDst);
  504. while True do
  505. begin
  506. d[count] := 0;
  507. inc(count);
  508. if count = 0 then exit;
  509. c := s[count];
  510. a := c shr 24;
  511. if a <> 0 then break;
  512. end;
  513. end;
  514. 255: // Special handling for 255 => no color change
  515. begin
  516. // Win32: Load stack variable into CPU register
  517. s := PColor32Array(pSrc);
  518. d := PColor32Array(pDst);
  519. if s = d then // if source=dest, we can skip writing to d
  520. begin
  521. while True do
  522. begin
  523. //d[count] := c; // skip the write
  524. inc(count);
  525. if count = 0 then exit;
  526. c := s[count];
  527. a := c shr 24;
  528. if a <> 255 then break;
  529. end;
  530. end
  531. else
  532. begin
  533. while True do
  534. begin
  535. d[count] := c;
  536. inc(count);
  537. if count = 0 then exit;
  538. c := s[count];
  539. a := c shr 24;
  540. if a <> 255 then break;
  541. end;
  542. end;
  543. end;
  544. else
  545. // Premultiply the alpha channel
  546. // Win32: Load stack variable into CPU register
  547. s := PColor32Array(pSrc);
  548. // Win32: This line "breaks" Delphi's register allocator
  549. //d := PColor32Array(pDst);
  550. while True do
  551. begin
  552. tab := @MulTable[a];
  553. c := (c and $FF000000) or
  554. (tab[Byte(c shr 16)] shl 16) or
  555. (tab[Byte(c shr 8)] shl 8) or
  556. (tab[Byte(c )] );
  557. //d[count] := c;
  558. PColor32Array(pDst)[count] := c;
  559. inc(count);
  560. if count = 0 then exit;
  561. c := s[count];
  562. a := c shr 24;
  563. if (a = 0) or (a = 255) then break;
  564. end;
  565. end;
  566. end;
  567. end;
  568. {$IFDEF RANGECHECKS_ENABLED}
  569. {$RANGECHECKS ON}
  570. {$ENDIF RANGECHECKS_ENABLED}
  571. //------------------------------------------------------------------------------
  572. // BoxDownSampling and related functions
  573. //------------------------------------------------------------------------------
  574. function GetWeightedColor(const srcBits: TArrayOfColor32;
  575. x256, y256, xx256, yy256, maxX: Integer): TColor32;
  576. var
  577. i, j, xi, yi, xxi, yyi, weight: Integer;
  578. xf, yf, xxf, yyf: cardinal;
  579. color: TWeightedColor;
  580. begin
  581. //This function performs 'box sampling' and differs from GetWeightedPixel
  582. //(bilinear resampling) in one important aspect - it accommodates weighting
  583. //any number of pixels (rather than just adjacent pixels) and this produces
  584. //better image quality when significantly downsizing.
  585. //Note: there's no range checking here, so the precondition is that the
  586. //supplied boundary values are within the bounds of the srcBits array.
  587. color.Reset;
  588. xi := x256 shr 8; xf := x256 and $FF;
  589. yi := y256 shr 8; yf := y256 and $FF;
  590. xxi := xx256 shr 8; xxf := xx256 and $FF;
  591. yyi := yy256 shr 8; yyf := yy256 and $FF;
  592. //1. average the corners ...
  593. weight := (($100 - xf) * ($100 - yf)) shr 8;
  594. color.Add(srcBits[xi + yi * maxX], weight);
  595. weight := (xxf * ($100 - yf)) shr 8;
  596. if (weight <> 0) then color.Add(srcBits[xxi + yi * maxX], weight);
  597. weight := (($100 - xf) * yyf) shr 8;
  598. if (weight <> 0) then color.Add(srcBits[xi + yyi * maxX], weight);
  599. weight := (xxf * yyf) shr 8;
  600. if (weight <> 0) then color.Add(srcBits[xxi + yyi * maxX], weight);
  601. //2. average the edges
  602. if (yi +1 < yyi) then
  603. begin
  604. xf := $100 - xf;
  605. for i := yi + 1 to yyi - 1 do
  606. color.Add(srcBits[xi + i * maxX], xf);
  607. if (xxf <> 0) then
  608. for i := yi + 1 to yyi - 1 do
  609. color.Add(srcBits[xxi + i * maxX], xxf);
  610. end;
  611. if (xi + 1 < xxi) then
  612. begin
  613. yf := $100 - yf;
  614. for i := xi + 1 to xxi - 1 do
  615. color.Add(srcBits[i + yi * maxX], yf);
  616. if (yyf <> 0) then
  617. for i := xi + 1 to xxi - 1 do
  618. color.Add(srcBits[i + yyi * maxX], yyf);
  619. end;
  620. //3. average the non-fractional pixel 'internals' ...
  621. for i := xi + 1 to xxi - 1 do
  622. for j := yi + 1 to yyi - 1 do
  623. color.Add(srcBits[i + j * maxX], $100);
  624. //4. finally get the weighted color ...
  625. if color.AddCount = 0 then
  626. Result := srcBits[xi + yi * maxX] else
  627. Result := color.Color;
  628. end;
  629. //------------------------------------------------------------------------------
  630. procedure BoxDownSampling(Image: TImage32; scaleX, scaleY: double);
  631. begin
  632. BoxDownSampling(Image, Image, scaleX, scaleY);
  633. end;
  634. //------------------------------------------------------------------------------
  635. procedure BoxDownSampling(Image: TImage32; scale: double);
  636. begin
  637. BoxDownSampling(Image, Image, scale);
  638. end;
  639. //------------------------------------------------------------------------------
  640. procedure BoxDownSampling(Image: TImage32; newWidth, newHeight: Integer);
  641. begin
  642. BoxDownSampling(Image, Image, newWidth, newHeight);
  643. end;
  644. //------------------------------------------------------------------------------
  645. procedure BoxDownSampling(Image, TargetImage: TImage32; scaleX, scaleY: double);
  646. begin
  647. BoxDownSampling(Image, TargetImage,
  648. Max(1, Integer(Round(Image.Width * scaleX))),
  649. Max(1, Integer(Round(Image.Height * scaleY))));
  650. end;
  651. //------------------------------------------------------------------------------
  652. procedure BoxDownSampling(Image, TargetImage: TImage32; scale: double);
  653. begin
  654. BoxDownSampling(Image, TargetImage,
  655. Max(1, Integer(Round(Image.Width * scale))),
  656. Max(1, Integer(Round(Image.Height * scale))));
  657. end;
  658. //------------------------------------------------------------------------------
  659. procedure BoxDownSampling(Image, TargetImage: TImage32; newWidth, newHeight: Integer);
  660. var
  661. x,y, x256,y256,xx256,yy256: Integer;
  662. sx,sy: double;
  663. tmp: TArrayOfColor32;
  664. pc: PColor32;
  665. scaledX: TArrayOfInteger;
  666. begin
  667. sx := Image.Width/newWidth * 256;
  668. sy := Image.Height/newHeight * 256;
  669. NewColor32Array(tmp, newWidth * newHeight, True);
  670. NewIntegerArray(scaledX, newWidth, True);
  671. for x := 0 to newWidth -1 do
  672. scaledX[x] := Round((x+1) * sx);
  673. y256 := 0;
  674. pc := @tmp[0];
  675. for y := 0 to newHeight - 1 do
  676. begin
  677. x256 := 0;
  678. yy256 := Round((y+1) * sy);
  679. for x := 0 to newWidth - 1 do
  680. begin
  681. xx256 := scaledX[x];
  682. pc^ := GetWeightedColor(Image.Pixels,
  683. x256, y256, xx256, yy256, Image.Width);
  684. x256 := xx256;
  685. inc(pc);
  686. end;
  687. y256 := yy256;
  688. end;
  689. TargetImage.AssignPixelArray(tmp, newWidth, newHeight);
  690. end;
  691. //------------------------------------------------------------------------------
  692. procedure NearestNeighborResize(Image: TImage32; newWidth, newHeight: Integer);
  693. begin
  694. NearestNeighborResize(Image, Image, newWidth, newHeight);
  695. end;
  696. //------------------------------------------------------------------------------
  697. procedure NearestNeighborResize(Image, TargetImage: TImage32; newWidth, newHeight: Integer);
  698. var
  699. x, y, offset: Integer;
  700. scaledXi, scaledYiOffset: TArrayOfInteger;
  701. tmp: TArrayOfColor32;
  702. pc: PColor32;
  703. pixels: TArrayOfColor32;
  704. begin
  705. //this NearestNeighbor code is slightly more efficient than
  706. //the more general purpose one in Img32.Resamplers
  707. if (newWidth = Image.Width) and (newHeight = Image.Height) then
  708. begin
  709. if TargetImage <> Image then TargetImage.Assign(Image);
  710. Exit;
  711. end;
  712. NewColor32Array(tmp, newWidth * newHeight, True);
  713. //get scaled X & Y values once only (storing them in lookup arrays) ...
  714. NewIntegerArray(scaledXi, newWidth, True);
  715. for x := 0 to newWidth -1 do
  716. scaledXi[x] := (x * Image.Width) div newWidth;
  717. NewIntegerArray(scaledYiOffset, newHeight, True);
  718. SetLength(scaledYiOffset, newHeight);
  719. for y := 0 to newHeight -1 do
  720. //scaledYiOffset[y] := Round(y * Image.Height / newHeight) * Image.Width;
  721. scaledYiOffset[y] := ((y * Image.Height) div newHeight) * Image.Width;
  722. pc := @tmp[0];
  723. pixels := Image.Pixels;
  724. for y := 0 to newHeight - 1 do
  725. begin
  726. offset := scaledYiOffset[y];
  727. for x := 0 to newWidth - 1 do
  728. begin
  729. pc^ := pixels[scaledXi[x] + offset];
  730. inc(pc);
  731. end;
  732. end;
  733. TargetImage.AssignPixelArray(tmp, newWidth, newHeight);
  734. end;
  735. //------------------------------------------------------------------------------
  736. procedure ResamplerResize(Image: TImage32; newWidth, newHeight: Integer);
  737. begin
  738. ResamplerResize(Image, Image, newWidth, newHeight);
  739. end;
  740. //------------------------------------------------------------------------------
  741. procedure ResamplerResize(Image, TargetImage: TImage32; newWidth, newHeight: Integer);
  742. var
  743. mat: TMatrixD;
  744. begin
  745. mat := IdentityMatrix;
  746. MatrixScale(mat, newWidth/Image.Width, newHeight/Image.Height);
  747. AffineTransformImage(Image, TargetImage, mat);
  748. end;
  749. //------------------------------------------------------------------------------
  750. //------------------------------------------------------------------------------
  751. procedure InitByteExponents;
  752. var
  753. i: integer;
  754. const
  755. inv255 : double = 1/255;
  756. inv255sqrd : double = 1/(255*255);
  757. inv255cubed: double = 1/(255*255*255);
  758. piDiv256 : double = Pi / 256;
  759. begin
  760. for i := 0 to 255 do
  761. begin
  762. byteFrac[i] := i *inv255;
  763. byteFracSq[i] := i*i *inv255sqrd;
  764. byteFracCubed[i] := i*i*i *inv255cubed;
  765. sinWeighted[i] := Round((Sin(i * piDiv256 - Pi/2) +1) /2 * 255);
  766. end;
  767. end;
  768. //------------------------------------------------------------------------------
  769. initialization
  770. InitByteExponents;
  771. rNearestResampler := RegisterResampler(NearestResampler, 'NearestNeighbor');
  772. rBilinearResampler := RegisterResampler(BilinearResample, 'Bilinear');
  773. rBicubicResampler := RegisterResampler(BicubicResample, 'HermiteBicubic');
  774. rWeightedBilinear := RegisterResampler(WeightedBilinearResample, 'WeightedBilinear');
  775. DefaultResampler := rBilinearResampler;
  776. end.