fpquantizer.pas 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787
  1. {*****************************************************************************}
  2. {
  3. This file is part of the Free Pascal's "Free Components Library".
  4. Copyright (c) 2005 by Giulio Bernardi
  5. This file contains classes used to quantize images.
  6. See the file COPYING.FPC, included in this distribution,
  7. for details about the copyright.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  11. }
  12. {*****************************************************************************}
  13. {$mode objfpc}{$h+}
  14. unit FPQuantizer;
  15. interface
  16. uses sysutils, classes, fpimage, fpcolhash;
  17. type
  18. FPQuantizerException = class (exception);
  19. type
  20. TFPQuantizerProgressEvent = procedure (Sender: TObject; Stage: TFPImgProgressStage; PercentDone: Byte;
  21. const Msg: AnsiString; var Continue : Boolean) of object;
  22. type
  23. TFPColorQuantizer = class
  24. private
  25. FOnProgress : TFPQuantizerProgressEvent;
  26. protected
  27. FColNum : longword;
  28. FSupportsAlpha : boolean;
  29. FImages : array of TFPCustomImage;
  30. FCount : integer;
  31. function InternalQuantize : TFPPalette; virtual; abstract;
  32. procedure SetColNum(AColNum : longword); virtual;
  33. procedure Progress (Sender: TObject; Stage: TFPImgProgressStage; PercentDone: Byte; const Msg: AnsiString; var Continue : Boolean); virtual;
  34. function GetImage(Index : integer) : TFPCustomImage;
  35. procedure SetImage(Index : integer; const Img : TFPCustomImage);
  36. procedure SetCount(Value : integer);
  37. public
  38. property OnProgress : TFPQuantizerProgressEvent read FOnProgress write FOnProgress;
  39. property Images[Index : integer] : TFPCustomImage read GetImage write SetImage;
  40. property Count : integer read FCount write SetCount;
  41. property ColorNumber : longword read FColNum write SetColNum;
  42. property SupportsAlpha : boolean read FSupportsAlpha;
  43. procedure Clear;
  44. procedure Add(const Img : TFPCustomImage);
  45. function Quantize : TFPPalette;
  46. constructor Create; virtual;
  47. destructor Destroy; override;
  48. end;
  49. type
  50. POctreeQNode = ^TOctreeQNode;
  51. TOctreeQChilds = array[0..7] of POctreeQNode;
  52. TOctreeQNode = record
  53. isleaf : boolean;
  54. count : longword;
  55. R, G, B : longword;
  56. Next : POctreeQNode; //used in the reduction list.
  57. Childs : TOctreeQChilds;
  58. end;
  59. type
  60. TFPOctreeQuantizer = class(TFPColorQuantizer)
  61. private
  62. Root : POctreeQNode;
  63. ReductionList : TOctreeQChilds;
  64. LeafTot, MaxLeaf : longword;
  65. percent : byte; { these values are used to call OnProgress event }
  66. percentinterval : longword;
  67. percentacc : longword;
  68. FContinue : boolean;
  69. procedure DisposeNode(var Node : POctreeQNode);
  70. procedure AddColor(var Node : POctreeQNode; const R, G, B, Level : byte);
  71. procedure AddToPalette(var Node : POctreeQNode; Palette : TFPPalette; var Current : integer);
  72. procedure Reduce;
  73. function BuildPalette : TFPPalette;
  74. protected
  75. function InternalQuantize : TFPPalette; override;
  76. public
  77. end;
  78. type
  79. TMCBox = record
  80. total, startindex, endindex : longword;
  81. end;
  82. const mcSlow = 0;
  83. mcNormal = 1;
  84. mcFast = 2;
  85. type
  86. TFPMedianCutQuantizer = class(TFPColorQuantizer)
  87. private
  88. HashTable, palcache : TFPColorHashTable;
  89. arr : TFPColorWeightArray;
  90. boxes : array of TMCBox;
  91. Used : integer;
  92. percent : byte; { these values are used to call OnProgress event }
  93. percentinterval : longword;
  94. percentacc : longword;
  95. FContinue : boolean;
  96. FMode : byte;
  97. function ColorCompare(const c1, c2 : TFPPackedColor; const Dim : byte) : shortint;
  98. function FindLargestDimension(const Box : TMCBox) : byte;
  99. procedure QuickSort(const l, r : integer; const Dim : byte);
  100. procedure QuickSortBoxes(const l, r : integer);
  101. function MeanBox(const box : TMCBox) : TFPColor;
  102. function BuildPalette : TFPPalette;
  103. procedure SetMode(const Amode : byte);
  104. function MaskColor(const col : TFPColor) : TFPColor;
  105. protected
  106. function InternalQuantize : TFPPalette; override;
  107. public
  108. constructor Create; override;
  109. property Mode : byte read FMode write SetMode;
  110. end;
  111. implementation
  112. function RGB2FPColor(const R, G, B : longword) : TFPColor;
  113. begin
  114. Result.Red:=(R shl 8) + R;
  115. Result.Green:=(G shl 8) + G;
  116. Result.Blue:=(B shl 8) + B;
  117. Result.Alpha := AlphaOpaque;
  118. end;
  119. { TFPColorQuantizer }
  120. function TFPColorQuantizer.Quantize : TFPPalette;
  121. begin
  122. Result:=InternalQuantize;
  123. end;
  124. constructor TFPColorQuantizer.Create;
  125. begin
  126. FSupportsAlpha:=false;
  127. FColNum:=256; //default setting.
  128. FCount:=0;
  129. setlength(FImages,0);
  130. end;
  131. destructor TFPColorQuantizer.Destroy;
  132. begin
  133. Setlength(FImages,0);
  134. inherited Destroy;
  135. end;
  136. procedure TFPColorQuantizer.SetColNum(AColNum : longword);
  137. begin
  138. if AColNum<2 then
  139. raise FPQuantizerException.Create('Invalid color depth: '+IntToStr(AColNum));
  140. FColNum:=AColNum;
  141. end;
  142. procedure TFPColorQuantizer.Progress(Sender: TObject; Stage: TFPImgProgressStage; PercentDone: Byte; const Msg: AnsiString; var Continue : Boolean);
  143. begin
  144. if Assigned(FOnProgress) then
  145. FOnProgress(Sender,Stage,PercentDone,Msg,Continue);
  146. end;
  147. function TFPColorQuantizer.GetImage(Index : integer) : TFPCustomImage;
  148. begin
  149. if Index>=FCount then
  150. raise FPQuantizerException.Create('Invalid image index: '+IntToStr(Index));
  151. Result:=FImages[index];
  152. end;
  153. procedure TFPColorQuantizer.SetImage(Index : integer; const Img : TFPCustomImage);
  154. begin
  155. if Index>=FCount then
  156. raise FPQuantizerException.Create('Invalid image index: '+IntToStr(Index));
  157. FImages[Index]:=Img;
  158. end;
  159. procedure TFPColorQuantizer.SetCount(Value : integer);
  160. var old, i : integer;
  161. begin
  162. old:=FCount;
  163. setlength(FImages,Value);
  164. for i:=old to Value-1 do
  165. FImages[i]:=nil;
  166. FCount:=Value;
  167. end;
  168. procedure TFPColorQuantizer.Clear;
  169. begin
  170. setlength(FImages,0);
  171. FCount:=0;
  172. end;
  173. procedure TFPColorQuantizer.Add(const Img : TFPCustomImage);
  174. var i : integer;
  175. begin
  176. { Find first unused slot }
  177. for i:=0 to FCount-1 do
  178. if FImages[i]=nil then
  179. begin
  180. Fimages[i]:=Img;
  181. exit;
  182. end;
  183. { If we reached this point there are no unused slot: let's enlarge the array }
  184. SetCount(Fcount+1);
  185. FImages[FCount-1]:=Img;
  186. end;
  187. { TFPOctreeQuantizer }
  188. const Mask : array[0..7] of byte = ($80, $40, $20, $10, $08, $04, $02, $01);
  189. procedure TFPOctreeQuantizer.AddColor(var Node : POctreeQNode; const R, G, B, Level : byte);
  190. var index, shift : byte;
  191. begin
  192. if Node=nil then
  193. begin
  194. Node:=getmem(sizeof(TOctreeQNode));
  195. if Node=nil then
  196. raise FPQuantizerException.Create('Out of memory');
  197. FillByte(Node^,sizeof(TOctreeQNode),0);
  198. if level=7 then
  199. begin
  200. Node^.isleaf:=true;
  201. inc(LeafTot); { we just created a new leaf }
  202. end
  203. else
  204. begin { we don't put leaves in reduction list since this is unuseful }
  205. Node^.isleaf:=false;
  206. Node^.Next:=ReductionList[level]; { added on top of the reduction list for its level }
  207. ReductionList[level]:=Node;
  208. end;
  209. end;
  210. if Node^.isleaf then
  211. begin
  212. inc(Node^.R,R);
  213. inc(Node^.G,G);
  214. inc(Node^.B,B);
  215. inc(Node^.count);
  216. end
  217. else
  218. begin
  219. shift:=7-level;
  220. index:=((R and mask[level]) shr shift) shl 2;
  221. index:=index+((G and mask[level]) shr shift) shl 1;
  222. index:=index+((B and mask[level]) shr shift);
  223. AddColor(Node^.Childs[index],R,G,B,Level+1);
  224. end;
  225. end;
  226. procedure TFPOctreeQuantizer.DisposeNode(var Node : POctreeQNode);
  227. var i : integer;
  228. begin
  229. if Node=nil then exit;
  230. if not (Node^.isleaf) then
  231. for i:=0 to 7 do
  232. if Node^.childs[i]<>nil then
  233. DisposeNode(Node^.childs[i]);
  234. FreeMem(Node);
  235. Node:=nil;
  236. end;
  237. procedure TFPOctreeQuantizer.Reduce;
  238. var i : integer;
  239. Node : POctreeQNode;
  240. begin
  241. i:=6; { level 7 nodes don't have childs, start from 6 and go backward }
  242. while ((i>0) and (ReductionList[i]=nil)) do
  243. dec(i);
  244. { remove this node from the list}
  245. Node:=ReductionList[i];
  246. ReductionList[i]:=Node^.Next;
  247. for i:=0 to 7 do
  248. if Node^.childs[i]<>nil then
  249. begin
  250. inc(Node^.count,Node^.childs[i]^.count);
  251. inc(Node^.r,Node^.childs[i]^.r);
  252. inc(Node^.g,Node^.childs[i]^.g);
  253. inc(Node^.b,Node^.childs[i]^.b);
  254. DisposeNode(Node^.childs[i]);
  255. dec(LeafTot);
  256. end;
  257. Node^.isleaf:=true;
  258. inc(LeafTot); { this node is now a leaf! }
  259. end;
  260. procedure TFPOctreeQuantizer.AddToPalette(var Node : POctreeQNode; Palette : TFPPalette; var Current : integer);
  261. var i : byte;
  262. begin
  263. if not FContinue then exit;
  264. if Node^.isleaf then
  265. begin
  266. if (current >= LeafTot) then
  267. raise FPQuantizerException.Create('Octree Quantizer internal error: palette index too high.');
  268. Node^.r:= Node^.r div Node^.count;
  269. Node^.g:= Node^.g div Node^.count;
  270. Node^.b:= Node^.b div Node^.count;
  271. Palette.Color[Current]:=RGB2FPColor(Node^.r,Node^.g,Node^.b);
  272. inc(current);
  273. { ************************************************ }
  274. inc(percentacc);
  275. if percentacc>=percentinterval then
  276. begin
  277. dec(percentacc,percentinterval);
  278. inc(percent);
  279. Progress(self,psRunning,percent,'',FContinue);
  280. end;
  281. { ************************************************ }
  282. end
  283. else
  284. for i:=0 to 7 do
  285. if Node^.childs[i]<>nil then
  286. AddToPalette(Node^.childs[i],Palette,Current);
  287. end;
  288. function TFPOctreeQuantizer.BuildPalette : TFPPalette;
  289. var pal : TFPPalette;
  290. i : integer;
  291. begin
  292. if Root=nil then exit;
  293. pal:=TFPPalette.Create(LeafTot);
  294. i:=0;
  295. try
  296. AddToPalette(Root,pal,i);
  297. except
  298. pal.Free;
  299. pal:=nil;
  300. raise;
  301. end;
  302. if not FContinue then
  303. begin
  304. pal.Free;
  305. pal:=nil;
  306. end;
  307. Result:=pal;
  308. end;
  309. function TFPOctreeQuantizer.InternalQuantize : TFPPalette;
  310. var i, j, k : integer;
  311. color : TFPColor;
  312. begin
  313. Root:=nil;
  314. for i:=0 to high(ReductionList) do
  315. ReductionList[i]:=nil;
  316. LeafTot:=0;
  317. MaxLeaf:=FColNum;
  318. { ************************************************************** }
  319. { set up some values useful when calling OnProgress event }
  320. { number of operations is: }
  321. { width*heigth for population }
  322. { initial palette count - final palette count for reduction }
  323. { final palette count for building the palette }
  324. { total: width*heigth+initial palette count. }
  325. { if source image doesn't have a palette assume palette count as }
  326. { width*height (worst scenario) if it is < 2^24, or 2^24 else }
  327. percentinterval:=0;
  328. percentacc:=0;
  329. for i:=0 to FCount-1 do
  330. if FImages[i]<>nil then
  331. begin
  332. percentinterval:=percentinterval+FImages[i].Width*FImages[i].Height;
  333. if FImages[i].UsePalette then
  334. percentacc:=percentacc+FImages[i].Palette.Count
  335. else
  336. percentacc:=percentacc+FImages[i].Width*FImages[i].Height;
  337. end;
  338. if percentacc>$1000000 then percentacc:=$1000000;
  339. percentinterval:=(percentacc+percentinterval) div 100; { how many operations for 1% }
  340. if percentinterval=0 then percentinterval:=$FFFFFFFF; { it's quick, call progress only when starting and ending }
  341. percent:=0;
  342. percentacc:=0;
  343. FContinue:=true;
  344. Progress (self,psStarting,0,'',FContinue);
  345. Result:=nil;
  346. if not FContinue then exit;
  347. { ************************************************************** }
  348. { populate the octree with colors }
  349. try
  350. for k:=0 to FCount-1 do
  351. if FImages[k]<>nil then
  352. for j:=0 to FImages[k].Height-1 do
  353. for i:=0 to FImages[k].Width-1 do
  354. begin
  355. Color:=FImages[k][i,j];
  356. AddColor(Root,(Color.Red and $FF00) shr 8,(Color.Green and $FF00) shr 8,(Color.Blue and $FF00) shr 8,0);
  357. { ************************************************* }
  358. inc(percentacc);
  359. if percentacc>=percentinterval then
  360. begin
  361. dec(percentacc,percentinterval);
  362. inc(percent);
  363. Progress(self,psRunning,percent,'',FContinue);
  364. if not FContinue then exit;
  365. end;
  366. { ************************************************* }
  367. end;
  368. { reduce number of colors until it is <= MaxLeaf }
  369. while LeafTot > MaxLeaf do
  370. begin
  371. Reduce;
  372. { ************************************************* }
  373. inc(percentacc);
  374. if percentacc>=percentinterval then
  375. begin
  376. dec(percentacc,percentinterval);
  377. inc(percent);
  378. Progress(self,psRunning,percent,'',FContinue);
  379. if not FContinue then exit;
  380. end;
  381. { ************************************************* }
  382. end;
  383. { build the palette }
  384. Result:=BuildPalette;
  385. if FContinue then Progress (self,psEnding,100,'',FContinue);
  386. finally
  387. DisposeNode(Root);
  388. end;
  389. end;
  390. { TFPMedianCutQuantizer }
  391. const DIM_ALPHA = 0;
  392. DIM_RED = 1;
  393. DIM_GREEN = 2;
  394. DIM_BLUE = 3;
  395. constructor TFPMedianCutQuantizer.Create;
  396. begin
  397. inherited Create;
  398. FSupportsAlpha:=true;
  399. FMode:=mcNormal;
  400. end;
  401. procedure TFPMedianCutQuantizer.SetMode(const Amode : byte);
  402. begin
  403. if not (Amode in [mcSlow,mcNormal,mcFast]) then
  404. raise FPQuantizerException.Create('Invalid quantizer mode: '+IntToStr(Amode));
  405. FMode:=Amode;
  406. end;
  407. function TFPMedianCutQuantizer.FindLargestDimension(const Box : TMCBox) : byte;
  408. var i : longword;
  409. col : TFPPackedColor;
  410. maxa, mina, maxr, minr, maxg, ming, maxb, minb : byte;
  411. begin
  412. maxa:=0; maxr:=0; maxg:=0; maxb:=0;
  413. mina:=$FF; minr:=$FF; ming:=$FF; minb:=$FF;
  414. for i:=box.startindex to box.endindex do
  415. begin
  416. col:=arr[i]^.Col;
  417. if col.A<mina then mina:=col.A;
  418. if col.A>maxa then maxa:=col.A;
  419. if col.R<minr then minr:=col.R;
  420. if col.R>maxr then maxr:=col.R;
  421. if col.G<ming then ming:=col.G;
  422. if col.G>maxg then maxg:=col.G;
  423. if col.B<minb then minb:=col.B;
  424. if col.B>maxb then maxb:=col.B;
  425. end;
  426. maxa:=maxa-mina;
  427. maxr:=maxr-minr;
  428. maxg:=maxg-ming;
  429. maxb:=maxb-minb;
  430. if ((maxa>maxr) and (maxa>maxg) and (maxa>maxb)) then Result:=DIM_ALPHA
  431. else if ((maxr>maxa) and (maxr>maxg) and (maxr>maxb)) then Result:=DIM_RED
  432. else if ((maxg>maxa) and (maxg>maxr) and (maxg>maxb)) then Result:=DIM_GREEN
  433. else Result:=DIM_BLUE;
  434. end;
  435. function TFPMedianCutQuantizer.ColorCompare(const c1, c2 : TFPPackedColor; const Dim : byte) : shortint;
  436. var tmp : integer;
  437. begin
  438. case Dim of
  439. DIM_ALPHA : tmp:=(c1.A-c2.A);
  440. DIM_RED : tmp:=(c1.R-c2.R);
  441. DIM_GREEN : tmp:=(c1.G-c2.G);
  442. DIM_BLUE : tmp:=(c1.B-c2.B)
  443. else raise FPQuantizerException.Create('Invalid dimension: '+IntToStr(Dim));
  444. end;
  445. if tmp>0 then Result:=1
  446. else if tmp<0 then Result:=-1
  447. else Result:=0;
  448. end;
  449. procedure TFPMedianCutQuantizer.QuickSort(const l, r : integer; const Dim : byte);
  450. var i, j : integer;
  451. pivot, temp : PFPColorWeight;
  452. begin
  453. if l<r then
  454. begin
  455. pivot:=arr[l];
  456. i:=l+1;
  457. j:=r;
  458. repeat
  459. while ((i<=r) and (ColorCompare(arr[i]^.Col,pivot^.Col,dim)<=0)) do
  460. inc(i);
  461. while (ColorCompare(arr[j]^.Col,pivot^.Col,dim)=1) do
  462. dec(j);
  463. if i<j then
  464. begin
  465. temp:=arr[i];
  466. arr[i]:=arr[j];
  467. arr[j]:=temp;
  468. end;
  469. until i > j;
  470. { don't swap if they are equal }
  471. if ColorCompare(arr[j]^.Col,pivot^.Col,dim)<>0 then
  472. begin
  473. arr[l]:=arr[j];
  474. arr[j]:=pivot;
  475. end;
  476. Quicksort(l,j-1,dim);
  477. Quicksort(i,r,dim);
  478. end;
  479. end;
  480. procedure TFPMedianCutQuantizer.QuickSortBoxes(const l, r : integer);
  481. var i, j : integer;
  482. pivot, temp : TMCBox;
  483. begin
  484. if l<r then
  485. begin
  486. pivot:=boxes[l];
  487. i:=l+1;
  488. j:=r;
  489. repeat
  490. while ((i<=r) and (boxes[i].total>=pivot.total)) do
  491. inc(i);
  492. while (boxes[j].total<pivot.total) do
  493. dec(j);
  494. if i<j then
  495. begin
  496. temp:=boxes[i];
  497. boxes[i]:=boxes[j];
  498. boxes[j]:=temp;
  499. end;
  500. until i > j;
  501. { don't swap if they are equal }
  502. if boxes[j].total<>pivot.total then
  503. begin
  504. boxes[l]:=boxes[j];
  505. boxes[j]:=pivot;
  506. end;
  507. QuicksortBoxes(l,j-1);
  508. QuicksortBoxes(i,r);
  509. end;
  510. end;
  511. function TFPMedianCutQuantizer.MeanBox(const box : TMCBox) : TFPColor;
  512. var tota,totr,totg,totb, pixcount : longword;
  513. i : integer;
  514. col : TFPPackedColor;
  515. fpcol : TFPColor;
  516. begin
  517. tota:=0; totr:=0; totg:=0; totb:=0; pixcount:=0;
  518. for i:=box.startindex to box.endindex do
  519. begin
  520. tota:=tota+(arr[i]^.Col.A*arr[i]^.Num);
  521. totr:=totr+(arr[i]^.Col.R*arr[i]^.Num);
  522. totg:=totg+(arr[i]^.Col.G*arr[i]^.Num);
  523. totb:=totb+(arr[i]^.Col.B*arr[i]^.Num);
  524. inc(pixcount,arr[i]^.Num);
  525. end;
  526. tota:=round(tota / pixcount);
  527. totr:=round(totr / pixcount);
  528. totg:=round(totg / pixcount);
  529. totb:=round(totb / pixcount);
  530. if tota>$FF then tota:=$FF;
  531. if totr>$FF then totr:=$FF;
  532. if totg>$FF then totg:=$FF;
  533. if totb>$FF then totb:=$FF;
  534. col.a:=tota;
  535. col.r:=totr;
  536. col.g:=totg;
  537. col.b:=totb;
  538. fpcol:=Packed2FPColor(col);
  539. if palcache.Get(fpcol)<>nil then { already found, try the middle color }
  540. begin
  541. fpcol:=Packed2FPColor(arr[(box.startindex+box.endindex) div 2]^.Col);
  542. if palcache.Get(fpcol)<>nil then { already found, try the first unused color }
  543. for i:=box.startindex to box.endindex do
  544. begin
  545. col.a:=arr[i]^.Col.A;
  546. col.r:=arr[i]^.Col.R;
  547. col.g:=arr[i]^.Col.G;
  548. col.b:=arr[i]^.Col.B;
  549. fpcol:=Packed2FPColor(col);
  550. if palcache.Get(fpcol)=nil then break;
  551. end;
  552. end;
  553. palcache.Insert(fpcol,nil);
  554. Result:=fpcol;
  555. end;
  556. function TFPMedianCutQuantizer.BuildPalette : TFPPalette;
  557. var pal : TFPPalette;
  558. i : integer;
  559. begin
  560. pal:=TFPPalette.Create(Used);
  561. try
  562. palcache:=TFPColorHashTable.Create;
  563. try
  564. for i:=0 to Used-1 do
  565. begin
  566. pal.Color[i]:=MeanBox(boxes[i]);
  567. { ************************************************* }
  568. inc(percentacc);
  569. if percentacc>=percentinterval then
  570. begin
  571. percentacc:=percentacc mod percentinterval;
  572. inc(percent);
  573. Progress(self,psRunning,percent,'',FContinue);
  574. if not FContinue then exit;
  575. end;
  576. { ************************************************* }
  577. end
  578. finally
  579. palcache.Free;
  580. end;
  581. except
  582. pal.Free;
  583. raise;
  584. end;
  585. Result:=pal;
  586. end;
  587. { slow mode: no filtering
  588. normal mode: 8 bit r, 6 bit g, 6 bit b
  589. fast mode: 5 bit r, 5 bit g, 5 bit b }
  590. const mask_r_normal = $FFFF;
  591. mask_g_normal = $FCFC;
  592. mask_b_normal = $FCFC;
  593. mask_r_fast = $F8F8;
  594. mask_g_fast = $F8F8;
  595. mask_b_fast = $F8F8;
  596. function TFPMedianCutQuantizer.MaskColor(const col : TFPColor) : TFPColor;
  597. begin
  598. case FMode of
  599. mcNormal:
  600. begin
  601. Result.Red:=Col.Red and mask_r_normal;
  602. Result.Green:=Col.Green and mask_g_normal;
  603. Result.Blue:=Col.Blue and mask_b_normal;
  604. end;
  605. mcFast:
  606. begin
  607. Result.Red:=Col.Red and mask_r_fast;
  608. Result.Green:=Col.Green and mask_g_fast;
  609. Result.Blue:=Col.Blue and mask_b_fast;
  610. end
  611. else Result:=Col;
  612. end;
  613. end;
  614. function TFPMedianCutQuantizer.InternalQuantize : TFPPalette;
  615. var box : ^TMCBox;
  616. i, j, k : integer;
  617. dim : byte;
  618. boxpercent : longword;
  619. begin
  620. HashTable:=TFPColorHashTable.Create;
  621. try
  622. { *****************************************************************************
  623. Operations:
  624. width*height of each image (populate the hash table)
  625. number of desired colors for the box creation process (this should weight as the previous step)
  626. number of desired colors for building the palette.
  627. }
  628. percentinterval:=0;
  629. for k:=0 to FCount-1 do
  630. if FImages[k]<>nil then
  631. percentinterval:=percentinterval+FImages[k].Height*FImages[k].Width;
  632. boxpercent:=percentinterval div FColNum;
  633. percentinterval:=percentinterval*2+FColNum;
  634. percentinterval:=percentinterval div 100; { how many operations for 1% }
  635. if percentinterval=0 then percentinterval:=$FFFFFFFF; { it's quick, call progress only when starting and ending }
  636. percent:=0;
  637. percentacc:=0;
  638. FContinue:=true;
  639. Progress (self,psStarting,0,'',FContinue);
  640. if not FContinue then exit;
  641. { ***************************************************************************** }
  642. { For every color in the images, count how many pixels use it}
  643. for k:=0 to FCount-1 do
  644. if FImages[k]<>nil then
  645. for j:=0 to FImages[k].Height-1 do
  646. for i:=0 to FImages[k].Width-1 do
  647. begin
  648. HashTable.Add(MaskColor(FImages[k][i,j]),1);
  649. { ************************************************* }
  650. inc(percentacc);
  651. if percentacc>=percentinterval then
  652. begin
  653. percentacc:=percentacc mod percentinterval;
  654. inc(percent);
  655. Progress(self,psRunning,percent,'',FContinue);
  656. if not FContinue then exit;
  657. end;
  658. { ************************************************* }
  659. end;
  660. { Then let's have the list in array form }
  661. setlength(arr,0);
  662. arr:=HashTable.GetArray;
  663. try
  664. HashTable.Clear; { free some resources }
  665. setlength(boxes,FColNum);
  666. boxes[0].startindex:=0;
  667. boxes[0].endindex:=length(arr)-1;
  668. boxes[0].total:=boxes[0].endindex+1;
  669. Used:=1;
  670. while (used<FColNum) do
  671. begin
  672. box:=nil;
  673. { find a box with at least 2 colors }
  674. for i:=0 to Used-1 do
  675. if (boxes[i].total)>=2 then
  676. begin
  677. box:=@boxes[i];
  678. break;
  679. end;
  680. if box=nil then break;
  681. dim:=FindLargestDimension(box^);
  682. { sort the colors of the box along the largest dimension }
  683. QuickSort(box^.startindex,box^.endindex,dim);
  684. { Split the box: half of the colors in the first one, the rest in the second one }
  685. j:=(box^.startindex+box^.endindex) div 2;
  686. { This is the second box }
  687. boxes[Used].startindex:=j+1;
  688. boxes[Used].endindex:=box^.endindex;
  689. boxes[Used].total:=box^.endindex-j;
  690. { And here we update the first box }
  691. box^.endindex:=j;
  692. box^.total:=box^.endindex-box^.startindex+1;
  693. { Sort the boxes so that the first one is the one with higher number of colors }
  694. QuickSortBoxes(0,Used);
  695. inc(Used);
  696. { ************************************************* }
  697. inc(percentacc,boxpercent);
  698. if percentacc>=percentinterval then
  699. begin
  700. inc(percent,percentacc div percentinterval);
  701. percentacc:=percentacc mod percentinterval;
  702. Progress(self,psRunning,percent,'',FContinue);
  703. if not FContinue then exit;
  704. end;
  705. { ************************************************* }
  706. end;
  707. Result:=BuildPalette;
  708. if FContinue then Progress (self,psEnding,100,'',FContinue);
  709. finally
  710. setlength(boxes,0);
  711. for i:=0 to length(arr)-1 do
  712. FreeMem(arr[i]);
  713. setlength(arr,0);
  714. end;
  715. finally
  716. HashTable.Free;
  717. end;
  718. end;
  719. end.