source: trunk/Packages/bgrabitmap/bgracolorquantization.pas

Last change on this file was 2, checked in by chronos, 5 years ago
File size: 63.2 KB
Line 
1unit BGRAColorQuantization;
2
3{$mode objfpc}{$H+}
4
5interface
6
7uses
8 Classes, SysUtils, BGRAPalette, BGRABitmapTypes;
9
10type
11 TBGRAColorBox = class;
12 TBGRAColorTree = class;
13 TBGRAApproxPalette = class;
14 TBiggestLeafMethod = (blMix, blApparentInterval, blWeight);
15
16 { TDimensionMinMax }
17
18 TDimensionMinMax = object
19 Minimum: UInt32;
20 Maximum: UInt32;
21 function Size: UInt32;
22 function Contains(AValue: UInt32): boolean;
23 function PointLike: boolean;
24 procedure SetAsPoint(AValue: UInt32);
25 function GetCenter: UInt32;
26 procedure GrowToInclude(AValue: UInt32);
27 end;
28
29 TColorDimension = (cdFast,cdRed,cdGreen,cdBlue,cdAlpha,cdRGB,cdRG,cdGB,cdRB,cdRInvG,cdGInvB,cdRInvB,cdRInvGB,cdGInvRB,cdBInvRG,
30 cdSaturation);
31 TColorDimensions = set of TColorDimension;
32
33 { TBGRAColorQuantizer }
34
35 TBGRAColorQuantizer = class(TBGRACustomColorQuantizer)
36 private
37 FColors: ArrayOfWeightedColor;
38 FPalette: TBGRAApproxPalette;
39 FReductionColorCount: Integer;
40 FReductionKeepContrast: boolean;
41 FSeparateAlphaChannel: boolean;
42 procedure Init(ABox: TBGRAColorBox);
43 procedure NormalizeArrayOfColors(AColors: ArrayOfTBGRAPixel; ARedBounds, AGreenBounds, ABlueBounds, AAlphaBounds: TDimensionMinMax; AUniform: boolean); overload;
44 procedure NormalizeArrayOfColors(AColors: ArrayOfTBGRAPixel; AColorBounds, AAlphaBounds: TDimensionMinMax); overload;
45 protected
46 function GetPalette: TBGRACustomApproxPalette; override;
47 function GetSourceColor(AIndex: integer): TBGRAPixel; override;
48 function GetSourceColorCount: Integer; override;
49 function GetReductionColorCount: integer; override;
50 procedure SetReductionColorCount(AValue: Integer); override;
51 public
52 constructor Create(APalette: TBGRACustomPalette; ASeparateAlphaChannel: boolean); override;
53 constructor Create(ABitmap: TBGRACustomBitmap; AAlpha: TAlphaChannelPaletteOption); override;
54 constructor Create(APalette: TBGRACustomPalette; ASeparateAlphaChannel: boolean; AReductionColorCount: integer); override;
55 constructor Create(ABitmap: TBGRACustomBitmap; AAlpha: TAlphaChannelPaletteOption; AReductionColorCount: integer); override;
56 destructor Destroy; override;
57 procedure ApplyDitheringInplace(AAlgorithm: TDitheringAlgorithm; ABitmap: TBGRACustomBitmap; ABounds: TRect); override;
58 function GetDitheredBitmap(AAlgorithm: TDitheringAlgorithm; ABitmap: TBGRACustomBitmap; ABounds: TRect): TBGRACustomBitmap; overload; override;
59 function GetDitheredBitmapIndexedData(ABitDepth: integer; AByteOrder: TRawImageByteOrder; AAlgorithm: TDitheringAlgorithm;
60 ABitmap: TBGRACustomBitmap; out AScanlineSize: PtrInt): Pointer; overload; override;
61 procedure SaveBitmapToStream(AAlgorithm: TDitheringAlgorithm;
62 ABitmap: TBGRACustomBitmap; AStream: TStream; AFormat: TBGRAImageFormat); override;
63 end;
64
65 { TBGRAApproxPalette }
66
67 TBGRAApproxPalette = class(TBGRACustomApproxPalette)
68 private
69 FTree: TBGRAColorTree;
70 FColors: ArrayOfWeightedColor;
71 protected
72 function GetCount: integer; override;
73 function GetColorByIndex(AIndex: integer): TBGRAPixel; override;
74 function GetWeightByIndex(AIndex: Integer): UInt32; override;
75 procedure Init(const AColors: ArrayOfTBGRAPixel);
76 public
77 constructor Create(const AColors: ArrayOfTBGRAPixel); overload;
78 constructor Create(const AColors: ArrayOfWeightedColor); overload;
79 constructor Create(AOwnedSplitTree: TBGRAColorTree); overload;
80 destructor Destroy; override;
81 function ContainsColor(AValue: TBGRAPixel): boolean; override;
82 function IndexOfColor(AValue: TBGRAPixel): integer; override;
83 function FindNearestColor(AValue: TBGRAPixel): TBGRAPixel; override;
84 function FindNearestColorIndex(AValue: TBGRAPixel): integer; override;
85 function GetAsArrayOfColor: ArrayOfTBGRAPixel; override;
86 function GetAsArrayOfWeightedColor: ArrayOfWeightedColor; override;
87 end;
88
89 { TBGRAApproxPaletteViaLargerPalette }
90
91 TBGRAApproxPaletteViaLargerPalette = class(TBGRAApproxPalette)
92 private
93 FLarger: TBGRACustomApproxPalette;
94 FLargerColors: array of record
95 approxColor: TBGRAPixel;
96 approxColorIndex: integer;
97 end;
98 FLargerOwned: boolean;
99 FTransparentColorIndex: integer;
100 protected
101 function FindNearestLargerColorIndex(AValue: TBGRAPixel): integer; virtual;
102 function SlowFindNearestColorIndex(AValue: TBGRAPixel): integer;
103 public
104 constructor Create(const AColors: ArrayOfTBGRAPixel; ALarger: TBGRACustomApproxPalette; ALargerOwned: boolean);
105 destructor Destroy; override;
106 function FindNearestColor(AValue: TBGRAPixel): TBGRAPixel; override;
107 function FindNearestColorIndex(AValue: TBGRAPixel): integer; override;
108 function GetAsArrayOfWeightedColor: ArrayOfWeightedColor; override;
109 end;
110
111 TIsChannelStrictlyGreaterFunc = TBGRAPixelComparer;
112 TIsChannelGreaterThanOrEqualToValueFunc = function (p : PBGRAPixel; v: UInt32): boolean;
113
114 TColorBoxBounds = array[TColorDimension] of TDimensionMinMax;
115
116 { TBGRAColorBox }
117
118 TBGRAColorBox = class
119 private
120 FBounds: TColorBoxBounds;
121 FTotalWeight: UInt32;
122 FColors: ArrayOfWeightedColor;
123 FDimensions: TColorDimensions;
124 FPureTransparentColorCount: integer;
125 function GetApparentInterval(ADimension: TColorDimension): UInt32;
126 function GetAverageColor: TBGRAPixel;
127 function GetAverageColorOrMainColor: TBGRAPixel;
128 function GetBounds(ADimension: TColorDimension): TDimensionMinMax;
129 function GetColorCount(ACountPureTransparent: boolean): integer;
130 function GetHasPureTransparentColor: boolean;
131 function GetInferiorColor: TBGRAPixel;
132 function GetLargestApparentDimension: TColorDimension;
133 function GetLargestApparentInterval: UInt32;
134 function GetPointLike: boolean;
135 function GetSuperiorColor: TBGRAPixel;
136 procedure Init(AColors: ArrayOfWeightedColor; AOwner: boolean);
137 procedure SortBy(ADimension: TColorDimension);
138 function GetMedianIndex(ADimension : TColorDimension; AMinValue, AMaxValue: UInt32): integer;
139 public
140 constructor Create(ADimensions: TColorDimensions; AColors: ArrayOfWeightedColor; AOwner: boolean); overload;
141 constructor Create(ADimensions: TColorDimensions; const AColors: ArrayOfTBGRAPixel; AAlpha: TAlphaChannelPaletteOption = acFullChannelInPalette); overload;
142 constructor Create(ADimensions: TColorDimensions; ABounds: TColorBoxBounds); overload;
143 constructor Create(ADimensions: TColorDimensions; APalette: TBGRACustomPalette); overload;
144 constructor Create(ADimensions: TColorDimensions; ABitmap: TBGRACustomBitmap; AAlpha: TAlphaChannelPaletteOption); overload;
145 constructor Create(ADimensions: TColorDimensions; AColors: PBGRAPixel; ANbPixels: integer; AAlpha: TAlphaChannelPaletteOption); overload;
146 function BoundsContain(AColor: TBGRAPixel): boolean;
147 function MedianCut(ADimension: TColorDimension; out SuperiorMiddle: UInt32): TBGRAColorBox;
148 function Duplicate : TBGRAColorBox;
149 property Bounds[ADimension: TColorDimension]: TDimensionMinMax read GetBounds;
150 property ApparentInterval[AChannel: TColorDimension]: UInt32 read GetApparentInterval;
151 property LargestApparentDimension: TColorDimension read GetLargestApparentDimension;
152 property LargestApparentInterval: UInt32 read GetLargestApparentInterval;
153 property PointLike: boolean read GetPointLike;
154 property AverageColor: TBGRAPixel read GetAverageColor;
155 property SuperiorColor: TBGRAPixel read GetSuperiorColor;
156 property InferiorColor: TBGRAPixel read GetInferiorColor;
157 property AverageColorOrMainColor: TBGRAPixel read GetAverageColorOrMainColor;
158 function GetAsArrayOfColors(AIncludePureTransparent: boolean): ArrayOfTBGRAPixel;
159 property TotalWeight: UInt32 read FTotalWeight;
160 property ColorCount[ACountPureTransparent: boolean]: integer read GetColorCount;
161 property HasPureTransparentColor: boolean read GetHasPureTransparentColor;
162 property PureTransparentColorCount: integer read FPureTransparentColorCount;
163 end;
164
165 TBGRALeafColorMode = (lcAverage, lcCenter, lcExtremum, lcMix);
166
167 { TBGRAColorTree }
168
169 TBGRAColorTree = class
170 private
171 FLeaf: TBGRAColorBox;
172 FIsLeaf: boolean;
173 FLargestApparentInterval: integer;
174 FWeight: UInt32;
175
176 FLeafColor: TBGRAPixel;
177 FLeafColorIndex: integer;
178 FLeafColorComputed: boolean;
179 FMinBorder, FMaxBorder: array[TColorDimension] of boolean;
180 FCenterColor: TBGRAPixel;
181 FAverageColor: TBGRAPixel;
182
183 FPureTransparentColorCount: integer;
184 FPureTransparentColorIndex: integer;
185 FDimension: TColorDimension;
186 FPixelValueComparer: TIsChannelGreaterThanOrEqualToValueFunc;
187 FSuperiorMiddle: UInt32;
188 FInferiorBranch, FSuperiorBranch: TBGRAColorTree;
189 function GetApproximatedColorCount: integer;
190 function GetHasPureTransparentColor: boolean;
191 function GetLeafCount: integer;
192 procedure Init(ALeaf: TBGRAColorBox; AOwned: boolean);
193 procedure InternalComputeLeavesColor(ALeafColor: TBGRALeafColorMode; var AStartIndex: integer);
194 procedure CheckColorComputed;
195 public
196 constructor Create(ABox: TBGRAColorBox; AOwned: boolean); overload;
197 constructor Create(ADimensions: TColorDimensions; APalette: TBGRACustomPalette); overload;
198 constructor Create(ADimensions: TColorDimensions; ABitmap: TBGRACustomBitmap; AAlpha: TAlphaChannelPaletteOption); overload;
199 destructor Destroy; override;
200 procedure FreeLeaves;
201 function FindBiggestLeaf(AMethod: TBiggestLeafMethod): TBGRAColorTree;
202 property LargestApparentInterval: integer read FLargestApparentInterval;
203 property Weight: UInt32 read FWeight;
204 property IsLeaf: boolean read FIsLeaf;
205 function TrySplitLeaf: boolean;
206 procedure ComputeLeavesColor(ALeafColor: TBGRALeafColorMode);
207 function ApproximateColor(AColor: TBGRAPixel): TBGRAPixel;
208 function ApproximateColorIndex(AColor: TBGRAPixel): integer;
209 function GetAsArrayOfApproximatedColors: ArrayOfTBGRAPixel;
210 function GetAsArrayOfWeightedColors: ArrayOfWeightedColor;
211 procedure SplitIntoPalette(ACount: integer; AMethod: TBiggestLeafMethod;
212 ALeafColor: TBGRALeafColorMode);
213 function SplitIntoPaletteWithSubPalette(ACount: integer; AMethod: TBiggestLeafMethod;
214 ALeafColor: TBGRALeafColorMode; ASubPaletteCount: integer): ArrayOfTBGRAPixel;
215 property LeafCount: integer read GetLeafCount;
216 property ApproximatedColorCount: integer read GetApproximatedColorCount;
217 property HasPureTransparentColor: boolean read GetHasPureTransparentColor;
218 property PureTransparentColorCount: integer read FPureTransparentColorCount;
219 end;
220
221function GetPixelStrictComparer(ADimension: TColorDimension): TIsChannelStrictlyGreaterFunc;
222function GetPixelValueComparer(ADimension: TColorDimension): TIsChannelGreaterThanOrEqualToValueFunc;
223function BGRAColorCount(ABitmap: TBGRACustomBitmap; AAlpha: TAlphaChannelPaletteOption): integer;
224
225const AllColorDimensions = [cdRed,cdGreen,cdBlue,cdAlpha,cdRGB,cdRG,cdGB,cdRB,cdRInvG,cdGInvB,cdRInvB,cdRInvGB,cdGInvRB,cdBInvRG,
226 cdSaturation];
227
228implementation
229
230uses BGRADithering, FPimage, FPWriteBMP, BGRAWritePNG, math;
231
232const MedianMinPercentage = 0.2;
233
234const RedShift = 1;
235 GreenShift = 2;
236 AlphaShift = 1;
237 SaturationShift = 2;
238
239function GetDimensionValue(APixel: TBGRAPixel; ADimension: TColorDimension): UInt32;
240var v: UInt32;
241begin
242 case ADimension of
243 cdFast: result := DWord(APixel);
244 cdRed: result := GammaExpansionTab[APixel.red] shl RedShift;
245 cdGreen: result := GammaExpansionTab[APixel.green] shl GreenShift;
246 cdBlue: result := GammaExpansionTab[APixel.blue];
247 cdAlpha: result := (APixel.alpha + (APixel.alpha shl 8)) shl AlphaShift;
248 cdRGB: result := GammaExpansionTab[APixel.blue] + (GammaExpansionTab[APixel.red] shl RedShift) + (GammaExpansionTab[APixel.green] shl GreenShift);
249 cdRG: result := (GammaExpansionTab[APixel.red] shl RedShift) + (GammaExpansionTab[APixel.green] shl GreenShift);
250 cdGB: result := GammaExpansionTab[APixel.blue] + (GammaExpansionTab[APixel.green] shl GreenShift);
251 cdRB: result := (GammaExpansionTab[APixel.red] shl RedShift) + GammaExpansionTab[APixel.blue];
252 cdRInvG: result := (GammaExpansionTab[APixel.red] shl RedShift) + ((not GammaExpansionTab[APixel.green]) shl GreenShift);
253 cdGInvB: result := (GammaExpansionTab[APixel.green] shl GreenShift) + (not GammaExpansionTab[APixel.blue]);
254 cdRInvB: result := (GammaExpansionTab[APixel.red] shl RedShift) + (not GammaExpansionTab[APixel.blue]);
255 cdRInvGB: result := (GammaExpansionTab[APixel.red] shl RedShift) + ((not GammaExpansionTab[APixel.green]) shl GreenShift) + (not GammaExpansionTab[APixel.blue]);
256 cdGInvRB: result := (GammaExpansionTab[APixel.green] shl GreenShift) + ((not GammaExpansionTab[APixel.red]) shl RedShift) + (not GammaExpansionTab[APixel.blue]);
257 cdBInvRG: result := (GammaExpansionTab[APixel.blue]) + ((not GammaExpansionTab[APixel.red]) shl RedShift) + ((not GammaExpansionTab[APixel.green]) shl GreenShift);
258 cdSaturation: with GammaExpansion(APixel) do
259 begin
260 v := red;
261 if green>v then v := green;
262 if blue>v then v := blue;
263 result := v;
264 v := red;
265 if green<v then v := green;
266 if blue<v then v := blue;
267 result -= v;
268 result := result shl SaturationShift;
269 end
270 else raise exception.Create('Unknown dimension');
271 end;
272end;
273
274function IsRGBGreater(p1, p2: PBGRAPixel): boolean;
275begin
276 result := ((GammaExpansionTab[p1^.red] shl RedShift)+(GammaExpansionTab[p1^.green] shl GreenShift)+GammaExpansionTab[p1^.blue]) >
277 ((GammaExpansionTab[p2^.red] shl RedShift)+(GammaExpansionTab[p2^.green] shl GreenShift)+GammaExpansionTab[p2^.blue]);
278end;
279
280function IsRGBGreaterThanValue(p: PBGRAPixel;
281 v: UInt32): boolean;
282begin
283 with p^ do
284 result := ((GammaExpansionTab[red] shl RedShift)+(GammaExpansionTab[green] shl GreenShift)+GammaExpansionTab[blue]) >= v;
285end;
286
287function IsRGGreater(p1, p2: PBGRAPixel): boolean;
288begin
289 result := ((GammaExpansionTab[p1^.red] shl RedShift)+(GammaExpansionTab[p1^.green] shl GreenShift)) >
290 ((GammaExpansionTab[p2^.red] shl RedShift)+(GammaExpansionTab[p2^.green] shl GreenShift));
291end;
292
293function IsRGGreaterThanValue(p: PBGRAPixel;
294 v: UInt32): boolean;
295begin
296 with p^ do
297 result := ((GammaExpansionTab[red] shl RedShift)+(GammaExpansionTab[green] shl GreenShift)) >= v;
298end;
299
300function IsGBGreater(p1, p2: PBGRAPixel): boolean;
301begin
302 result := ((GammaExpansionTab[p1^.green] shl GreenShift)+GammaExpansionTab[p1^.blue]) >
303 ((GammaExpansionTab[p2^.green] shl GreenShift)+GammaExpansionTab[p2^.blue]);
304end;
305
306function IsGBGreaterThanValue(p: PBGRAPixel;
307 v: UInt32): boolean;
308begin
309 with p^ do
310 result := ((GammaExpansionTab[green] shl GreenShift)+GammaExpansionTab[blue]) >= v;
311end;
312
313function IsRBGreater(p1, p2: PBGRAPixel): boolean;
314begin
315 result := ((GammaExpansionTab[p1^.red] shl RedShift)+GammaExpansionTab[p1^.blue]) >
316 ((GammaExpansionTab[p2^.red] shl RedShift)+GammaExpansionTab[p2^.blue]);
317end;
318
319function IsRBGreaterThanValue(p: PBGRAPixel;
320 v: UInt32): boolean;
321begin
322 with p^ do
323 result := ((GammaExpansionTab[red] shl RedShift)+GammaExpansionTab[blue]) >= v;
324end;
325
326function IsRInvGGreater(p1, p2: PBGRAPixel
327 ): boolean;
328begin
329 result := (GammaExpansionTab[p1^.red]+ ((not GammaExpansionTab[p1^.green]) shl GreenShift)) >
330 (GammaExpansionTab[p2^.red]+((not GammaExpansionTab[p2^.green]) shl GreenShift));
331end;
332
333function IsRInvGGreaterThanValue(p: PBGRAPixel;
334 v: UInt32): boolean;
335begin
336 with p^ do
337 result := (GammaExpansionTab[red]+((not GammaExpansionTab[green]) shl GreenShift)) >= v;
338end;
339
340function IsGInvBGreater(p1, p2: PBGRAPixel
341 ): boolean;
342begin
343 result := (GammaExpansionTab[p1^.green] shl GreenShift + not GammaExpansionTab[p1^.blue]) >
344 (GammaExpansionTab[p2^.green] shl GreenShift + not GammaExpansionTab[p2^.blue]);
345end;
346
347function IsGInvBGreaterThanValue(p: PBGRAPixel;
348 v: UInt32): boolean;
349begin
350 with p^ do
351 result := (GammaExpansionTab[green] shl GreenShift + not GammaExpansionTab[blue]) >= v;
352end;
353
354function IsRInvBGreater(p1, p2: PBGRAPixel
355 ): boolean;
356begin
357 result := (GammaExpansionTab[p1^.red] shl RedShift + not GammaExpansionTab[p1^.blue]) >
358 (GammaExpansionTab[p2^.red] shl RedShift + not GammaExpansionTab[p2^.blue]);
359end;
360
361function IsRInvBGreaterThanValue(p: PBGRAPixel;
362 v: UInt32): boolean;
363begin
364 with p^ do
365 result := (GammaExpansionTab[red] shl RedShift + not GammaExpansionTab[blue]) >= v;
366end;
367
368function IsRInvGBGreater(p1, p2: PBGRAPixel
369 ): boolean;
370begin
371 result := (GammaExpansionTab[p1^.red] shl RedShift + ((not GammaExpansionTab[p1^.green]) shl GreenShift) + not GammaExpansionTab[p1^.blue]) >
372 (GammaExpansionTab[p2^.red] shl RedShift + ((not GammaExpansionTab[p2^.green]) shl GreenShift) + not GammaExpansionTab[p2^.blue]);
373end;
374
375function IsRInvGBGreaterThanValue(p: PBGRAPixel;
376 v: UInt32): boolean;
377begin
378 with p^ do
379 result := (GammaExpansionTab[red] shl RedShift + ((not GammaExpansionTab[green]) shl GreenShift) + not GammaExpansionTab[blue]) >= v;
380end;
381
382function IsGInvRBGreater(p1, p2: PBGRAPixel
383 ): boolean;
384begin
385 result := (GammaExpansionTab[p1^.green] shl GreenShift + ((not GammaExpansionTab[p1^.red]) shl RedShift) + not GammaExpansionTab[p1^.blue]) >
386 (GammaExpansionTab[p2^.green] shl GreenShift + ((not GammaExpansionTab[p2^.red]) shl RedShift) + not GammaExpansionTab[p2^.blue]);
387end;
388
389function IsGInvRBGreaterThanValue(p: PBGRAPixel;
390 v: UInt32): boolean;
391begin
392 with p^ do
393 result := (GammaExpansionTab[green] shl GreenShift + ((not GammaExpansionTab[red]) shl RedShift) + not GammaExpansionTab[blue]) >= v;
394end;
395
396function IsBInvRGGreater(p1, p2: PBGRAPixel
397 ): boolean;
398begin
399 result := (GammaExpansionTab[p1^.blue] + ((not GammaExpansionTab[p1^.red]) shl RedShift) + ((not GammaExpansionTab[p1^.green]) shl GreenShift)) >
400 (GammaExpansionTab[p2^.blue] + ((not GammaExpansionTab[p2^.red]) shl RedShift) + ((not GammaExpansionTab[p2^.green]) shl GreenShift));
401end;
402
403function IsBInvRGGreaterThanValue(p: PBGRAPixel;
404 v: UInt32): boolean;
405begin
406 with p^ do
407 result := (GammaExpansionTab[blue] + ((not GammaExpansionTab[red]) shl RedShift) + ((not GammaExpansionTab[green]) shl GreenShift)) >= v;
408end;
409
410function IsSaturationGreater(p1, p2: PBGRAPixel): boolean;
411begin
412 result := GetDimensionValue(p1^,cdSaturation) > GetDimensionValue(p2^,cdSaturation);
413end;
414
415function IsSaturationGreaterThanValue(p: PBGRAPixel;
416 v: UInt32): boolean;
417begin
418 result := GetDimensionValue(p^,cdSaturation) >= v;
419end;
420
421function IsRedGreater(p1, p2: PBGRAPixel): boolean;
422begin
423 result := p1^.red > p2^.red;
424end;
425
426function IsRedGreaterThanValue(p: PBGRAPixel;
427 v: UInt32): boolean;
428begin
429 result := GammaExpansionTab[p^.red] shl RedShift >= v;
430end;
431
432function IsGreenGreater(p1, p2: PBGRAPixel
433 ): boolean;
434begin
435 result := p1^.green > p2^.green;
436end;
437
438function IsGreenGreaterThanValue(p: PBGRAPixel;
439 v: UInt32): boolean;
440begin
441 result := GammaExpansionTab[p^.green] shl GreenShift >= v;
442end;
443
444function IsBlueGreater(p1, p2: PBGRAPixel
445 ): boolean;
446begin
447 result := p1^.blue > p2^.blue;
448end;
449
450function IsBlueGreaterThanValue(p: PBGRAPixel;
451 v: UInt32): boolean;
452begin
453 result := GammaExpansionTab[p^.blue] >= v;
454end;
455
456function IsAlphaGreater(p1, p2: PBGRAPixel
457 ): boolean;
458begin
459 result := p1^.alpha > p2^.alpha;
460end;
461
462function IsAlphaGreaterThanValue(p: PBGRAPixel;
463 v: UInt32): boolean;
464begin
465 result := (p^.alpha + p^.alpha shl 8) shl AlphaShift >= v;
466end;
467
468function IsDWordGreater(p1, p2: PBGRAPixel
469 ): boolean;
470begin
471 result := DWord(p1^) > DWord(p2^);
472end;
473
474function IsDWordGreaterThanValue(p: PBGRAPixel;
475 v: UInt32): boolean;
476begin
477 result := DWord(p^) >= v;
478end;
479
480function GetPixelStrictComparer(ADimension: TColorDimension
481 ): TIsChannelStrictlyGreaterFunc;
482begin
483 case ADimension of
484 cdFast: result := @IsDWordGreater;
485 cdRed: result := @IsRedGreater;
486 cdGreen: result := @IsGreenGreater;
487 cdBlue: result := @IsBlueGreater;
488 cdAlpha: result := @IsAlphaGreater;
489 cdRGB: result := @IsRGBGreater;
490 cdRG: result := @IsRGGreater;
491 cdGB: result := @IsGBGreater;
492 cdRB: result := @IsRBGreater;
493 cdRInvG: result := @IsRInvGGreater;
494 cdGInvB: result := @IsGInvBGreater;
495 cdRInvB: result := @IsRInvBGreater;
496 cdRInvGB: result := @IsRInvGBGreater;
497 cdGInvRB: result := @IsGInvRBGreater;
498 cdBInvRG: result := @IsBInvRGGreater;
499 cdSaturation: result := @IsSaturationGreater;
500 else raise Exception.Create('Unknown dimension');
501 end;
502end;
503
504function GetPixelValueComparer(ADimension: TColorDimension
505 ): TIsChannelGreaterThanOrEqualToValueFunc;
506begin
507 case ADimension of
508 cdFast: result := @IsDWordGreaterThanValue;
509 cdRed: result := @IsRedGreaterThanValue;
510 cdGreen: result := @IsGreenGreaterThanValue;
511 cdBlue: result := @IsBlueGreaterThanValue;
512 cdAlpha: result := @IsAlphaGreaterThanValue;
513 cdRGB: result := @IsRGBGreaterThanValue;
514 cdRG: result := @IsRGGreaterThanValue;
515 cdGB: result := @IsGBGreaterThanValue;
516 cdRB: result := @IsRBGreaterThanValue;
517 cdRInvG: result := @IsRInvGGreaterThanValue;
518 cdGInvB: result := @IsGInvBGreaterThanValue;
519 cdRInvB: result := @IsRInvBGreaterThanValue;
520 cdRInvGB: result := @IsRInvGBGreaterThanValue;
521 cdGInvRB: result := @IsGInvRBGreaterThanValue;
522 cdBInvRG: result := @IsBInvRGGreaterThanValue;
523 cdSaturation: result := @IsSaturationGreaterThanValue;
524 else raise Exception.Create('Unknown dimension');
525 end;
526end;
527
528function BGRAColorCount(ABitmap: TBGRACustomBitmap;
529 AAlpha: TAlphaChannelPaletteOption): integer;
530var
531 box: TBGRAColorBox;
532begin
533 box := TBGRAColorBox.Create(AllColorDimensions,ABitmap,AAlpha);
534 result := box.ColorCount[True];
535 box.Free;
536end;
537
538const
539 ApproxPaletteDimensions = [cdAlpha,cdRInvG,cdGInvB,cdRInvB,cdRInvGB,cdGInvRB,cdBInvRG,cdRGB];
540
541{ TBGRAApproxPaletteViaLargerPalette }
542
543function TBGRAApproxPaletteViaLargerPalette.FindNearestLargerColorIndex(
544 AValue: TBGRAPixel): integer;
545begin
546 result := FLarger.FindNearestColorIndex(AValue);
547end;
548
549function TBGRAApproxPaletteViaLargerPalette.SlowFindNearestColorIndex(
550 AValue: TBGRAPixel): integer;
551var diff,curDiff: NativeInt;
552 i: NativeInt;
553begin
554 if AValue.alpha = 0 then
555 begin
556 result := FTransparentColorIndex;
557 exit;
558 end;
559 diff := BGRAWordDiff(AValue, FColors[0].Color);
560 result := 0;
561 for i := 0 to high(FColors) do
562 begin
563 curDiff := BGRAWordDiff(AValue, FColors[i].Color);
564 if curDiff < diff then
565 begin
566 result := i;
567 diff := curDiff;
568 end;
569 end;
570end;
571
572constructor TBGRAApproxPaletteViaLargerPalette.Create(
573 const AColors: ArrayOfTBGRAPixel; ALarger: TBGRACustomApproxPalette; ALargerOwned: boolean);
574var i: integer;
575 largeWeighted: ArrayOfWeightedColor;
576begin
577 inherited Create(AColors);
578 FTransparentColorIndex:= -1;
579 for i := 0 to high(FColors) do
580 begin
581 FColors[i].Weight := 0;
582 if FColors[i].Color.alpha = 0 then FTransparentColorIndex:= i;
583 end;
584 FLarger := ALarger;
585 FLargerOwned := ALargerOwned;
586 largeWeighted := FLarger.GetAsArrayOfWeightedColor;
587 setlength(FLargerColors, length(largeWeighted));
588 for i := 0 to high(FLargerColors) do
589 with FLargerColors[i] do
590 begin
591 approxColorIndex := SlowFindNearestColorIndex(largeWeighted[i].Color);
592 if approxColorIndex = -1 then
593 approxColor := BGRAPixelTransparent
594 else
595 begin
596 approxColor := FColors[approxColorIndex].Color;
597 inc(FColors[approxColorIndex].Weight, largeWeighted[i].Weight);
598 end;
599 end;
600end;
601
602destructor TBGRAApproxPaletteViaLargerPalette.Destroy;
603begin
604 if FLargerOwned then FreeAndNil(FLarger);
605 inherited Destroy;
606end;
607
608function TBGRAApproxPaletteViaLargerPalette.FindNearestColor(AValue: TBGRAPixel
609 ): TBGRAPixel;
610var index: integer;
611begin
612 index := FindNearestLargerColorIndex(AValue);
613 if index = -1 then
614 result := BGRAPixelTransparent
615 else
616 Result:= FLargerColors[index].approxColor;
617end;
618
619function TBGRAApproxPaletteViaLargerPalette.FindNearestColorIndex(
620 AValue: TBGRAPixel): integer;
621var index: integer;
622begin
623 index := FindNearestLargerColorIndex(AValue);
624 if index = -1 then
625 result := -1
626 else
627 Result:= FLargerColors[index].approxColorIndex;
628end;
629
630function TBGRAApproxPaletteViaLargerPalette.GetAsArrayOfWeightedColor: ArrayOfWeightedColor;
631var
632 i: Integer;
633begin
634 setlength(result, length(FColors));
635 for i := 0 to high(FColors) do
636 result[i] := FColors[i];
637end;
638
639{ TBGRAApproxPalette }
640
641function TBGRAApproxPalette.GetCount: integer;
642begin
643 result := length(FColors);
644end;
645
646function TBGRAApproxPalette.GetColorByIndex(AIndex: integer): TBGRAPixel;
647begin
648 if (AIndex < 0) or (AIndex >= length(FColors)) then
649 raise ERangeError.Create('Index out of bounds');
650 result := FColors[AIndex].Color;
651end;
652
653function TBGRAApproxPalette.GetWeightByIndex(AIndex: Integer): UInt32;
654begin
655 if (AIndex < 0) or (AIndex >= length(FColors)) then
656 raise ERangeError.Create('Index out of bounds');
657 result := FColors[AIndex].Weight;
658end;
659
660procedure TBGRAApproxPalette.Init(const AColors: ArrayOfTBGRAPixel);
661var
662 weightedColors: ArrayOfWeightedColor;
663 i: NativeInt;
664begin
665 setlength(weightedColors, length(AColors));
666 for i := 0 to high(weightedColors) do
667 with weightedColors[i] do
668 begin
669 Color := AColors[i];
670 Weight := 1;
671 end;
672 FTree := TBGRAColorTree.Create(TBGRAColorBox.Create(ApproxPaletteDimensions,weightedColors,True),True);
673 FTree.SplitIntoPalette(length(AColors),blApparentInterval,lcAverage);
674
675 FColors := FTree.GetAsArrayOfWeightedColors;
676end;
677
678constructor TBGRAApproxPalette.Create(const AColors: ArrayOfTBGRAPixel);
679begin
680 Init(AColors);
681end;
682
683constructor TBGRAApproxPalette.Create(const AColors: ArrayOfWeightedColor);
684begin
685 FTree := TBGRAColorTree.Create(TBGRAColorBox.Create(ApproxPaletteDimensions,AColors,True),True);
686 FTree.SplitIntoPalette(length(AColors),blApparentInterval,lcAverage);
687
688 FColors := FTree.GetAsArrayOfWeightedColors;
689end;
690
691constructor TBGRAApproxPalette.Create(AOwnedSplitTree: TBGRAColorTree);
692begin
693 FTree := AOwnedSplitTree;
694 FColors := FTree.GetAsArrayOfWeightedColors;
695end;
696
697destructor TBGRAApproxPalette.Destroy;
698begin
699 FreeAndNil(FTree);
700 inherited Destroy;
701end;
702
703function TBGRAApproxPalette.ContainsColor(AValue: TBGRAPixel): boolean;
704begin
705 result := (IndexOfColor(AValue)<>-1);
706end;
707
708function TBGRAApproxPalette.IndexOfColor(AValue: TBGRAPixel): integer;
709begin
710 result := FTree.ApproximateColorIndex(AValue);
711 if (result <> -1) and not (DWord(FColors[result].Color) = DWord(AValue)) then result := -1;
712end;
713
714function TBGRAApproxPalette.FindNearestColor(AValue: TBGRAPixel): TBGRAPixel;
715begin
716 result := FTree.ApproximateColor(AValue);
717end;
718
719function TBGRAApproxPalette.FindNearestColorIndex(AValue: TBGRAPixel): integer;
720begin
721 result := FTree.ApproximateColorIndex(AValue);
722end;
723
724function TBGRAApproxPalette.GetAsArrayOfColor: ArrayOfTBGRAPixel;
725var
726 i: NativeInt;
727begin
728 setlength(result, length(FColors));
729 for i := 0 to high(result) do
730 result[i] := FColors[i].Color;
731end;
732
733function TBGRAApproxPalette.GetAsArrayOfWeightedColor: ArrayOfWeightedColor;
734var
735 i: NativeInt;
736begin
737 if Assigned(FTree) then
738 result := FTree.GetAsArrayOfWeightedColors
739 else
740 begin
741 setlength(result, length(FColors));
742 for i := 0 to high(result) do
743 result[i] := FColors[i];
744 end;
745end;
746
747{ TBGRAColorQuantizer }
748
749procedure TBGRAColorQuantizer.Init(ABox: TBGRAColorBox);
750begin
751 FColors := ABox.FColors;
752 if ABox.HasPureTransparentColor then
753 begin
754 setlength(FColors,length(FColors)+1);
755 with FColors[high(FColors)] do
756 begin
757 Color := BGRAPixelTransparent;
758 Weight:= ABox.PureTransparentColorCount;
759 end;
760 end;
761 ABox.FColors := nil;
762 ABox.Free;
763 FReductionColorCount := 256;
764 FReductionKeepContrast := true;
765end;
766
767procedure TBGRAColorQuantizer.SetReductionColorCount(AValue: Integer);
768begin
769 if AValue < 1 then AValue := 1;
770 if FReductionColorCount=AValue then Exit;
771 FReductionColorCount:=AValue;
772 FreeAndNil(FPalette);
773end;
774
775procedure TBGRAColorQuantizer.NormalizeArrayOfColors(
776 AColors: ArrayOfTBGRAPixel; ARedBounds, AGreenBounds, ABlueBounds,
777 AAlphaBounds: TDimensionMinMax; AUniform: boolean);
778var
779 curRedBounds, curGreenBounds, curBlueBounds, curAlphaBounds: TDimensionMinMax;
780 RedSub,RedMul,RedDiv,RedAdd: NativeUInt;
781 GreenSub,GreenMul,GreenDiv,GreenAdd: NativeUInt;
782 BlueSub,BlueMul,BlueDiv,BlueAdd: NativeUInt;
783 AlphaSub,AlphaMul,AlphaDiv,AlphaAdd: NativeUInt;
784 i: NativeInt;
785 colorBounds: TDimensionMinMax;
786begin
787 if length(AColors)=0 then exit;
788 if AUniform then
789 begin
790 colorBounds := ABlueBounds;
791 colorBounds.GrowToInclude(AGreenBounds.Minimum shr GreenShift);
792 colorBounds.GrowToInclude(AGreenBounds.Maximum shr GreenShift);
793 colorBounds.GrowToInclude(ARedBounds.Minimum shr RedShift);
794 colorBounds.GrowToInclude(ARedBounds.Maximum shr RedShift);
795 NormalizeArrayOfColors(AColors, colorBounds, AAlphaBounds);
796 exit;
797 end;
798 curRedBounds.SetAsPoint(GetDimensionValue(AColors[0],cdRed));
799 curGreenBounds.SetAsPoint(GetDimensionValue(AColors[0],cdGreen));
800 curBlueBounds.SetAsPoint(GetDimensionValue(AColors[0],cdBlue));
801 curAlphaBounds.SetAsPoint(GetDimensionValue(AColors[0],cdAlpha));
802 for i := 1 to high(AColors) do
803 with AColors[i] do
804 begin
805 curRedBounds.GrowToInclude(GetDimensionValue(AColors[i],cdRed));
806 curGreenBounds.GrowToInclude(GetDimensionValue(AColors[i],cdGreen));
807 curBlueBounds.GrowToInclude(GetDimensionValue(AColors[i],cdBlue));
808 curAlphaBounds.GrowToInclude(GetDimensionValue(AColors[i],cdAlpha));
809 end;
810 RedSub := curRedBounds.Minimum shr RedShift;
811 RedMul := ARedBounds.Size shr RedShift;
812 RedDiv := curRedBounds.Size shr RedShift;
813 RedAdd := ARedBounds.Minimum shr RedShift;
814 if RedDiv = 0 then RedDiv := 1;
815 GreenSub := curGreenBounds.Minimum shr GreenShift;
816 GreenMul := AGreenBounds.Size shr GreenShift;
817 GreenDiv := curGreenBounds.Size shr GreenShift;
818 GreenAdd := AGreenBounds.Minimum shr GreenShift;
819 if GreenDiv = 0 then GreenDiv := 1;
820 BlueSub := curBlueBounds.Minimum;
821 BlueMul := ABlueBounds.Size;
822 BlueDiv := curBlueBounds.Size;
823 BlueAdd := ABlueBounds.Minimum;
824 if BlueDiv = 0 then BlueDiv := 1;
825 AlphaSub := curAlphaBounds.Minimum shr (AlphaShift+8);
826 AlphaMul := AAlphaBounds.Size shr (AlphaShift+8);
827 AlphaDiv := curAlphaBounds.Size shr (AlphaShift+8);
828 AlphaAdd := AAlphaBounds.Minimum shr (AlphaShift+8);
829 if AlphaDiv = 0 then AlphaDiv := 1;
830 for i := 0 to high(AColors) do
831 with AColors[i] do
832 begin
833 red := GammaCompressionTab[((GammaExpansionTab[red]-RedSub)*RedMul+(RedDiv shr 1)) div RedDiv + RedAdd];
834 green := GammaCompressionTab[((GammaExpansionTab[green]-GreenSub)*GreenMul+(GreenDiv shr 1)) div GreenDiv + GreenAdd];
835 blue := GammaCompressionTab[((GammaExpansionTab[blue]-BlueSub)*BlueMul+(BlueDiv shr 1)) div BlueDiv + BlueAdd];
836 alpha := ((alpha-AlphaSub)*AlphaMul+(AlphaDiv shr 1)) div AlphaDiv + AlphaAdd;
837 end;
838end;
839
840procedure TBGRAColorQuantizer.NormalizeArrayOfColors(
841 AColors: ArrayOfTBGRAPixel; AColorBounds, AAlphaBounds: TDimensionMinMax);
842var
843 curColorBounds, curAlphaBounds: TDimensionMinMax;
844 ColorSub,ColorMul,ColorDiv,ColorAdd: NativeUInt;
845 AlphaSub,AlphaMul,AlphaDiv,AlphaAdd: NativeUInt;
846 i: NativeInt;
847begin
848 if length(AColors)=0 then exit;
849 curColorBounds.SetAsPoint(GammaExpansionTab[AColors[0].red]);
850 curColorBounds.GrowToInclude(GammaExpansionTab[AColors[0].green]);
851 curColorBounds.GrowToInclude(GammaExpansionTab[AColors[0].blue]);
852 curAlphaBounds.SetAsPoint(AColors[0].alpha);
853 for i := 1 to high(AColors) do
854 with AColors[i] do
855 begin
856 curColorBounds.GrowToInclude(GammaExpansionTab[red]);
857 curColorBounds.GrowToInclude(GammaExpansionTab[green]);
858 curColorBounds.GrowToInclude(GammaExpansionTab[blue]);
859 curAlphaBounds.GrowToInclude(alpha);
860 end;
861 ColorSub := curColorBounds.Minimum;
862 ColorMul := AColorBounds.Size;
863 ColorDiv := curColorBounds.Size;
864 ColorAdd := AColorBounds.Minimum;
865 if ColorDiv = 0 then ColorDiv := 1;
866 AlphaSub := curAlphaBounds.Minimum;
867 AlphaMul := AAlphaBounds.Size shr 8;
868 AlphaDiv := curAlphaBounds.Size;
869 AlphaAdd := AAlphaBounds.Minimum shr 8;
870 if AlphaDiv = 0 then AlphaDiv := 1;
871 for i := 0 to high(AColors) do
872 with AColors[i] do
873 begin
874 red := GammaCompressionTab[((GammaExpansionTab[red]-ColorSub)*ColorMul+(ColorDiv shr 1)) div ColorDiv + ColorAdd];
875 green := GammaCompressionTab[((GammaExpansionTab[green]-ColorSub)*ColorMul+(ColorDiv shr 1)) div ColorDiv + ColorAdd];
876 blue := GammaCompressionTab[((GammaExpansionTab[blue]-ColorSub)*ColorMul+(ColorDiv shr 1)) div ColorDiv + ColorAdd];
877 alpha := ((alpha-AlphaSub)*AlphaMul+(AlphaDiv shr 1)) div AlphaDiv + AlphaAdd;
878 end;
879end;
880
881function TBGRAColorQuantizer.GetSourceColorCount: Integer;
882begin
883 result := length(FColors);
884end;
885
886function TBGRAColorQuantizer.GetReductionColorCount: integer;
887begin
888 result := FReductionColorCount;
889end;
890
891function TBGRAColorQuantizer.GetPalette: TBGRACustomApproxPalette;
892var
893 tree: TBGRAColorTree;
894
895 procedure MakeTreeErrorDiffusionFriendly;
896 var moreColors: ArrayOfWeightedColor;
897 box: TBGRAColorBox;
898 begin
899 moreColors := tree.GetAsArrayOfWeightedColors;
900 tree.free;
901 box := TBGRAColorBox.Create([cdRed,cdGreen,cdBlue,cdAlpha],moreColors,True);
902 tree := TBGRAColorTree.Create(box,True);
903 tree.SplitIntoPalette(box.ColorCount[true], blApparentInterval, lcAverage);
904 end;
905
906var
907 originalBox: TBGRAColorBox;
908 colors: ArrayOfTBGRAPixel;
909 bounds: array[TColorDimension] of TDimensionMinMax;
910 nbLarge,nbOriginal: integer;
911
912begin
913 if not Assigned(FPalette) then
914 if FReductionColorCount >= length(FColors) then
915 begin
916 originalBox := TBGRAColorBox.Create([cdRed,cdGreen,cdBlue,cdAlpha],FColors, False);
917 tree := TBGRAColorTree.Create(originalBox,True);
918 tree.SplitIntoPalette(originalBox.ColorCount[true], blApparentInterval, lcAverage);
919 FPalette := TBGRAApproxPalette.Create(tree);
920 end else
921 begin
922 originalBox := TBGRAColorBox.Create(AllColorDimensions, FColors, False);
923 bounds[cdRed] := originalBox.Bounds[cdRed];
924 bounds[cdGreen] := originalBox.Bounds[cdGreen];
925 bounds[cdBlue] := originalBox.Bounds[cdBlue];
926 bounds[cdAlpha] := originalBox.Bounds[cdAlpha];
927 if originalBox.HasPureTransparentColor then bounds[cdAlpha].Minimum := 0;
928 if FReductionColorCount = 1 then
929 begin
930 setlength(colors,1);
931 colors[0] := originalBox.AverageColor;
932 originalBox.Free;
933 FPalette := TBGRAApproxPalette.Create(colors);
934 end else
935 begin
936 tree := TBGRAColorTree.Create(originalBox,True);
937 if FReductionColorCount <= 64 then
938 begin
939 nbLarge := 128;
940 nbOriginal := originalBox.ColorCount[True];
941 if nbOriginal < 128 then nbLarge:= nbOriginal;
942 colors := tree.SplitIntoPaletteWithSubPalette(nbLarge, blMix,lcMix, FReductionColorCount);
943 MakeTreeErrorDiffusionFriendly;
944 if FReductionColorCount <= 4 then
945 NormalizeArrayOfColors(colors, bounds[cdRed],bounds[cdGreen],bounds[cdBlue],bounds[cdAlpha],true);
946 FPalette := TBGRAApproxPaletteViaLargerPalette.Create(colors, TBGRAApproxPalette.Create(tree), True);
947 end else
948 begin
949 tree.SplitIntoPalette(FReductionColorCount, blMix,lcMix);
950 MakeTreeErrorDiffusionFriendly;
951 FPalette := TBGRAApproxPalette.Create(tree);
952 end;
953 end;
954 end;
955 result := FPalette;
956end;
957
958function TBGRAColorQuantizer.GetSourceColor(AIndex: integer): TBGRAPixel;
959begin
960 if (AIndex < 0) or (AIndex >= length(FColors)) then
961 raise ERangeError.Create('Index out of bounds');
962 result := FColors[AIndex].Color;
963end;
964
965constructor TBGRAColorQuantizer.Create(APalette: TBGRACustomPalette; ASeparateAlphaChannel: boolean);
966begin
967 FSeparateAlphaChannel:= ASeparateAlphaChannel;
968 Init(TBGRAColorBox.Create(AllColorDimensions, APalette));
969end;
970
971constructor TBGRAColorQuantizer.Create(ABitmap: TBGRACustomBitmap; AAlpha: TAlphaChannelPaletteOption);
972begin
973 FSeparateAlphaChannel:= (AAlpha = acIgnore);
974 Init(TBGRAColorBox.Create(AllColorDimensions, ABitmap, AAlpha));
975end;
976
977constructor TBGRAColorQuantizer.Create(APalette: TBGRACustomPalette;
978 ASeparateAlphaChannel: boolean; AReductionColorCount: integer);
979begin
980 FSeparateAlphaChannel:= ASeparateAlphaChannel;
981 Init(TBGRAColorBox.Create(AllColorDimensions, APalette));
982 ReductionColorCount := AReductionColorCount;
983end;
984
985constructor TBGRAColorQuantizer.Create(ABitmap: TBGRACustomBitmap;
986 AAlpha: TAlphaChannelPaletteOption; AReductionColorCount: integer);
987begin
988 FSeparateAlphaChannel:= (AAlpha = acIgnore);
989 Init(TBGRAColorBox.Create(AllColorDimensions, ABitmap, AAlpha));
990 ReductionColorCount := AReductionColorCount;
991end;
992
993destructor TBGRAColorQuantizer.Destroy;
994begin
995 FreeAndNil(FPalette);
996 inherited Destroy;
997end;
998
999procedure TBGRAColorQuantizer.ApplyDitheringInplace(AAlgorithm: TDitheringAlgorithm; ABitmap: TBGRACustomBitmap;
1000 ABounds: TRect);
1001var task: TDitheringTask;
1002begin
1003 task := CreateDitheringTask(AAlgorithm, ABitmap, ReducedPalette, FSeparateAlphaChannel, ABounds);
1004 task.Destination := ABitmap;
1005 task.Execute;
1006 task.Free;
1007end;
1008
1009function TBGRAColorQuantizer.GetDitheredBitmap(AAlgorithm: TDitheringAlgorithm;
1010 ABitmap: TBGRACustomBitmap; ABounds: TRect): TBGRACustomBitmap;
1011var task: TDitheringTask;
1012begin
1013 task := CreateDitheringTask(AAlgorithm, ABitmap, ReducedPalette, FSeparateAlphaChannel, ABounds);
1014 result := task.Execute;
1015 task.Free;
1016end;
1017
1018function TBGRAColorQuantizer.GetDitheredBitmapIndexedData(
1019 ABitDepth: integer; AByteOrder: TRawImageByteOrder; AAlgorithm: TDitheringAlgorithm; ABitmap: TBGRACustomBitmap;
1020 out AScanlineSize: PtrInt): Pointer;
1021var
1022 indexer: TDitheringToIndexedImage;
1023begin
1024 indexer := TDitheringToIndexedImage.Create(ReducedPalette, FSeparateAlphaChannel, ABitDepth, AByteOrder);
1025 indexer.DefaultTransparentColorIndex := ReducedPalette.IndexOfColor(BGRAPixelTransparent);
1026 AScanlineSize:= indexer.ComputeMinimumScanlineSize(ABitmap.Width);
1027 result := indexer.DitherImage(AAlgorithm, ABitmap, AScanlineSize);
1028 indexer.Free;
1029end;
1030
1031procedure TBGRAColorQuantizer.SaveBitmapToStream(AAlgorithm: TDitheringAlgorithm; ABitmap: TBGRACustomBitmap;
1032 AStream: TStream; AFormat: TBGRAImageFormat);
1033var
1034 dithered: TBGRACustomBitmap;
1035 hasTransp: boolean;
1036 writer: TFPCustomImageWriter;
1037 depth: integer;
1038begin
1039 dithered := GetDitheredBitmap(AAlgorithm, ABitmap);
1040 try
1041 ReducedPalette.AssignTo(dithered);
1042 hasTransp := dithered.HasTransparentPixels;
1043 writer := CreateBGRAImageWriter(AFormat, hasTransp);
1044 try
1045 if writer is TBGRAWriterPNG then TBGRAWriterPNG(writer).Indexed := true else
1046 if writer is TFPWriterBMP then
1047 begin
1048 if not hasTransp then
1049 begin
1050 depth := BGRARequiredBitDepth(ReducedPalette);
1051 if depth < 8 then
1052 begin
1053 if depth > 4 then
1054 depth := 8
1055 else if depth > 1 then
1056 depth := 4;
1057 end;
1058 TFPWriterBMP(writer).BitsPerPixel := depth;
1059 end;
1060 end;
1061 dithered.SaveToStream(AStream, writer);
1062 finally
1063 writer.Free;
1064 end;
1065 finally
1066 dithered.Free;
1067 end;
1068end;
1069
1070{ TBGRAColorTree }
1071
1072function TBGRAColorTree.TrySplitLeaf: boolean;
1073var
1074 dim: TColorDimension;
1075 box2: TBGRAColorBox;
1076 mid: UInt32;
1077begin
1078 result := false;
1079 if IsLeaf and Assigned(FLeaf) and not FLeaf.PointLike then
1080 begin
1081 dim := FLeaf.LargestApparentDimension;
1082 box2 := FLeaf.MedianCut(dim,mid);
1083 if box2 <> nil then
1084 begin
1085 FInferiorBranch := TBGRAColorTree.Create(FLeaf,True);
1086 FSuperiorBranch := TBGRAColorTree.Create(box2,True);
1087
1088 FInferiorBranch.FMinBorder := FMinBorder;
1089 FInferiorBranch.FMaxBorder := FMaxBorder;
1090 FSuperiorBranch.FMinBorder := FMinBorder;
1091 FSuperiorBranch.FMaxBorder := FMaxBorder;
1092 FInferiorBranch.FMaxBorder[dim] := false;
1093 FSuperiorBranch.FMinBorder[dim] := false;
1094
1095 FLeaf := nil;
1096 FIsLeaf:= false;
1097 FDimension := dim;
1098 FPixelValueComparer := GetPixelValueComparer(FDimension);
1099 FSuperiorMiddle := mid;
1100 result := true;
1101 end;
1102 end;
1103end;
1104
1105procedure TBGRAColorTree.ComputeLeavesColor(ALeafColor: TBGRALeafColorMode);
1106var index: integer;
1107begin
1108 index := 0;
1109 if HasPureTransparentColor then
1110 begin
1111 FPureTransparentColorIndex:= index;
1112 inc(index);
1113 end;
1114 InternalComputeLeavesColor(ALeafColor,{%H-}index);
1115end;
1116
1117procedure TBGRAColorTree.InternalComputeLeavesColor(
1118 ALeafColor: TBGRALeafColorMode; var AStartIndex: integer);
1119var nbMin,nbMax: NativeInt;
1120 c: TColorDimension;
1121 extremumColor: TBGRAPixel;
1122 extremumColorRelevant: Boolean;
1123begin
1124 if IsLeaf then
1125 begin
1126 FLeafColorIndex := AStartIndex;
1127 inc(AStartIndex);
1128 if Assigned(FLeaf) then
1129 begin
1130 if not FLeafColorComputed then
1131 begin
1132 FLeafColorComputed := true;
1133 FCenterColor.alpha:= min(FLeaf.FBounds[cdAlpha].GetCenter shr AlphaShift, 255);
1134 FCenterColor.red:= GammaCompressionTab[min(FLeaf.FBounds[cdRed].GetCenter shr RedShift, 65535)];
1135 FCenterColor.green:= GammaCompressionTab[min(FLeaf.FBounds[cdGreen].GetCenter shr GreenShift, 65535)];
1136 FCenterColor.blue:= GammaCompressionTab[min(FLeaf.FBounds[cdBlue].GetCenter, 65535)];
1137 FAverageColor := FLeaf.AverageColorOrMainColor;
1138 extremumColor := FAverageColor;
1139
1140 if ALeafColor in [lcMix,lcExtremum] then
1141 begin
1142 nbMax := 0;
1143 nbMin := 0;
1144 for c := succ(low(TColorDimension)) to high(TColorDimension) do
1145 begin
1146 if FMinBorder[c] then inc(nbMin);
1147 if FMaxBorder[c] then inc(nbMax);
1148 end;
1149
1150 if nbMin > nbMax then
1151 extremumColor := FLeaf.InferiorColor
1152 else if nbMax > nbMin then
1153 extremumColor := FLeaf.SuperiorColor;
1154 end;
1155
1156 case ALeafColor of
1157 lcAverage,lcMix: FLeafColor := FAverageColor;
1158 lcExtremum: FLeafColor := extremumColor;
1159 else FLeafColor := FCenterColor;
1160 end;
1161
1162 if ALeafColor = lcMix then
1163 begin
1164 extremumColorRelevant := false;
1165 for c := succ(low(TColorDimension)) to high(TColorDimension) do
1166 if UInt32(abs(GetDimensionValue(extremumColor,c) - GetDimensionValue(FLeafColor,c))) >
1167 FLeaf.FBounds[c].Size div 7 then
1168 begin
1169 extremumColorRelevant := true;
1170 break;
1171 end;
1172 if extremumColorRelevant then FLeafColor := extremumColor;
1173 end;
1174 end;
1175 end else
1176 begin
1177 FLeafColor := BGRAPixelTransparent;
1178 FCenterColor := BGRAPixelTransparent;
1179 end;
1180 end else
1181 begin
1182 if Assigned(FInferiorBranch) then FInferiorBranch.InternalComputeLeavesColor(ALeafColor, AStartIndex);
1183 if Assigned(FSuperiorBranch) then FSuperiorBranch.InternalComputeLeavesColor(ALeafColor, AStartIndex);
1184 end;
1185end;
1186
1187procedure TBGRAColorTree.CheckColorComputed;
1188begin
1189 if not FLeafColorComputed then
1190 raise exception.Create('Color not computed. Call ComputeLeavesColor first.');
1191end;
1192
1193function TBGRAColorTree.ApproximateColor(AColor: TBGRAPixel): TBGRAPixel;
1194var branch: TBGRAColorTree;
1195begin
1196 if AColor.alpha = 0 then
1197 begin
1198 result := BGRAPixelTransparent;
1199 exit;
1200 end;
1201 if IsLeaf then
1202 begin
1203 CheckColorComputed;
1204 result := FLeafColor;
1205 end else
1206 begin
1207 if FPixelValueComparer(@AColor,FSuperiorMiddle) then
1208 branch := FSuperiorBranch else branch := FInferiorBranch;
1209 if Assigned(branch) then
1210 result := branch.ApproximateColor(AColor)
1211 else
1212 result := BGRAPixelTransparent;
1213 end;
1214end;
1215
1216function TBGRAColorTree.ApproximateColorIndex(AColor: TBGRAPixel): integer;
1217var branch: TBGRAColorTree;
1218begin
1219 if AColor.alpha = 0 then
1220 begin
1221 result := FPureTransparentColorIndex;
1222 exit;
1223 end;
1224 if IsLeaf then
1225 begin
1226 CheckColorComputed;
1227 result := FLeafColorIndex;
1228 end else
1229 begin
1230 if FPixelValueComparer(@AColor,FSuperiorMiddle) then
1231 branch := FSuperiorBranch else branch := FInferiorBranch;
1232 if Assigned(branch) then
1233 result := branch.ApproximateColorIndex(AColor)
1234 else
1235 result := FPureTransparentColorIndex;
1236 end;
1237end;
1238
1239function TBGRAColorTree.GetAsArrayOfApproximatedColors: ArrayOfTBGRAPixel;
1240var a,b: ArrayOfTBGRAPixel;
1241 idx,i: integer;
1242begin
1243 if IsLeaf then
1244 begin
1245 CheckColorComputed;
1246 setlength(result,1+byte(HasPureTransparentColor));
1247 idx := 0;
1248 if HasPureTransparentColor then
1249 begin
1250 result[idx] := BGRAPixelTransparent;
1251 inc(idx);
1252 end;
1253 result[idx] := FLeafColor;
1254 end else
1255 begin
1256 a := FInferiorBranch.GetAsArrayOfApproximatedColors;
1257 b := FSuperiorBranch.GetAsArrayOfApproximatedColors;
1258 setlength(result, length(a)+length(b)+byte(HasPureTransparentColor));
1259 idx := 0;
1260 if HasPureTransparentColor then
1261 begin
1262 result[idx] := BGRAPixelTransparent;
1263 inc(idx);
1264 end;
1265 for i := 0 to high(a) do
1266 begin
1267 result[idx] := a[i];
1268 inc(idx);
1269 end;
1270 for i := 0 to high(b) do
1271 begin
1272 result[idx] := b[i];
1273 inc(idx);
1274 end;
1275 end;
1276end;
1277
1278function TBGRAColorTree.GetAsArrayOfWeightedColors: ArrayOfWeightedColor;
1279var a,b: ArrayOfWeightedColor;
1280 idx,i: integer;
1281begin
1282 if IsLeaf then
1283 begin
1284 CheckColorComputed;
1285 setlength(result,1+byte(HasPureTransparentColor));
1286 idx := 0;
1287 if HasPureTransparentColor then
1288 begin
1289 result[idx].Color := BGRAPixelTransparent;
1290 result[idx].Weight := PureTransparentColorCount;
1291 inc(idx);
1292 end;
1293 result[idx].Color := FLeafColor;
1294 result[idx].Weight := Weight;
1295 end else
1296 begin
1297 a := FInferiorBranch.GetAsArrayOfWeightedColors;
1298 b := FSuperiorBranch.GetAsArrayOfWeightedColors;
1299 setlength(result, length(a)+length(b)+byte(HasPureTransparentColor));
1300 idx := 0;
1301 if HasPureTransparentColor then
1302 begin
1303 result[idx].Color := BGRAPixelTransparent;
1304 result[idx].Weight := PureTransparentColorCount;
1305 inc(idx);
1306 end;
1307 for i := 0 to high(a) do
1308 begin
1309 result[idx] := a[i];
1310 inc(idx);
1311 end;
1312 for i := 0 to high(b) do
1313 begin
1314 result[idx] := b[i];
1315 inc(idx);
1316 end;
1317 end;
1318end;
1319
1320procedure TBGRAColorTree.SplitIntoPalette(ACount: integer;
1321 AMethod: TBiggestLeafMethod; ALeafColor: TBGRALeafColorMode);
1322var nbColors: integer;
1323 leaf: TBGRAColorTree;
1324begin
1325 nbColors := ApproximatedColorCount;
1326 while nbColors < ACount do
1327 begin
1328 leaf := FindBiggestLeaf(AMethod);
1329 if not leaf.TrySplitLeaf then break;
1330 inc(nbColors);
1331 end;
1332 ComputeLeavesColor(ALeafColor);
1333 FreeLeaves;
1334end;
1335
1336function TBGRAColorTree.SplitIntoPaletteWithSubPalette(ACount: integer;
1337 AMethod: TBiggestLeafMethod; ALeafColor: TBGRALeafColorMode;
1338 ASubPaletteCount: integer): ArrayOfTBGRAPixel;
1339var nbColors: integer;
1340 leaf: TBGRAColorTree;
1341begin
1342 result := nil;
1343 nbColors := ApproximatedColorCount;
1344 if ASubPaletteCount > ACount then ASubPaletteCount:= ACount;
1345 if nbColors = ASubPaletteCount then
1346 begin
1347 ComputeLeavesColor(ALeafColor);
1348 result := GetAsArrayOfApproximatedColors;
1349 end;
1350 while nbColors < ACount do
1351 begin
1352 leaf := FindBiggestLeaf(AMethod);
1353 if not leaf.TrySplitLeaf then break;
1354 inc(nbColors);
1355 if nbColors = ASubPaletteCount then
1356 begin
1357 ComputeLeavesColor(ALeafColor);
1358 result := GetAsArrayOfApproximatedColors;
1359 end;
1360 end;
1361 ComputeLeavesColor(ALeafColor);
1362 FreeLeaves;
1363end;
1364
1365function TBGRAColorTree.GetLeafCount: integer;
1366begin
1367 if IsLeaf then
1368 result := 1
1369 else
1370 begin
1371 result := 0;
1372 if Assigned(FInferiorBranch) then result += FInferiorBranch.LeafCount;
1373 if Assigned(FSuperiorBranch) then result += FSuperiorBranch.LeafCount;
1374 end;
1375end;
1376
1377function TBGRAColorTree.GetApproximatedColorCount: integer;
1378begin
1379 if IsLeaf then
1380 result := 1
1381 else
1382 begin
1383 result := 0;
1384 if Assigned(FInferiorBranch) then result += FInferiorBranch.ApproximatedColorCount;
1385 if Assigned(FSuperiorBranch) then result += FSuperiorBranch.ApproximatedColorCount;
1386 end;
1387 if HasPureTransparentColor then inc(result);
1388end;
1389
1390function TBGRAColorTree.GetHasPureTransparentColor: boolean;
1391begin
1392 result := FPureTransparentColorCount > 0;
1393end;
1394
1395procedure TBGRAColorTree.Init(ALeaf: TBGRAColorBox; AOwned: boolean);
1396var
1397 c: TColorDimension;
1398begin
1399 if not AOwned then
1400 FLeaf := ALeaf.Duplicate
1401 else
1402 FLeaf := ALeaf;
1403 FLargestApparentInterval:= FLeaf.LargestApparentInterval;
1404 FWeight := FLeaf.TotalWeight;
1405 FIsLeaf:= true;
1406 for c := low(TColorDimension) to high(TColorDimension) do
1407 begin
1408 FMinBorder[c] := true;
1409 FMaxBorder[c] := true;
1410 end;
1411 FPureTransparentColorCount:= FLeaf.PureTransparentColorCount;
1412 FPureTransparentColorIndex:= -1;
1413end;
1414
1415constructor TBGRAColorTree.Create(ABox: TBGRAColorBox; AOwned: boolean);
1416begin
1417 Init(ABox,AOwned);
1418end;
1419
1420constructor TBGRAColorTree.Create(ADimensions: TColorDimensions; APalette: TBGRACustomPalette);
1421begin
1422 Init(TBGRAColorBox.Create(ADimensions, APalette),True);
1423end;
1424
1425constructor TBGRAColorTree.Create(ADimensions: TColorDimensions; ABitmap: TBGRACustomBitmap; AAlpha: TAlphaChannelPaletteOption);
1426begin
1427 Init(TBGRAColorBox.Create(ADimensions, ABitmap, AAlpha),True);
1428end;
1429
1430destructor TBGRAColorTree.Destroy;
1431begin
1432 FreeAndNil(FInferiorBranch);
1433 FreeAndNil(FSuperiorBranch);
1434 FreeAndNil(FLeaf);
1435 inherited Destroy;
1436end;
1437
1438procedure TBGRAColorTree.FreeLeaves;
1439begin
1440 if IsLeaf then
1441 FreeAndNil(FLeaf)
1442 else
1443 begin
1444 if Assigned(FInferiorBranch) then FInferiorBranch.FreeLeaves;
1445 if Assigned(FSuperiorBranch) then FSuperiorBranch.FreeLeaves;
1446 end;
1447end;
1448
1449function TBGRAColorTree.FindBiggestLeaf(AMethod: TBiggestLeafMethod
1450 ): TBGRAColorTree;
1451var infLeaf,supLeaf: TBGRAColorTree;
1452begin
1453 if IsLeaf then
1454 result := self
1455 else
1456 begin
1457 infLeaf := FInferiorBranch.FindBiggestLeaf(AMethod);
1458 supLeaf := FSuperiorBranch.FindBiggestLeaf(AMethod);
1459 case AMethod of
1460 blApparentInterval:
1461 if infLeaf.LargestApparentInterval >= supLeaf.LargestApparentInterval then
1462 result := infLeaf
1463 else
1464 result := supLeaf;
1465 blWeight:
1466 if (infLeaf.LargestApparentInterval > 0) and (infLeaf.Weight >= supLeaf.Weight) then
1467 result := infLeaf
1468 else
1469 result := supLeaf;
1470 else{blMix:}
1471 if (sqrt(infLeaf.Weight/FWeight)*(infLeaf.LargestApparentInterval/LargestApparentInterval) >=
1472 sqrt(supLeaf.Weight/FWeight)*(supLeaf.LargestApparentInterval/LargestApparentInterval) ) then
1473 result := infLeaf
1474 else
1475 result := supLeaf;
1476 end;
1477 end;
1478end;
1479
1480{ TDimensionMinMax }
1481
1482function TDimensionMinMax.Size: UInt32;
1483begin
1484 if Maximum>Minimum then
1485 result := Maximum-Minimum
1486 else
1487 result := 0;
1488end;
1489
1490function TDimensionMinMax.Contains(AValue: UInt32): boolean;
1491begin
1492 result := (AValue >= Minimum) and (AValue <= Maximum);
1493end;
1494
1495function TDimensionMinMax.PointLike: boolean;
1496begin
1497 result := (Minimum = Maximum);
1498end;
1499
1500procedure TDimensionMinMax.SetAsPoint(AValue: UInt32);
1501begin
1502 Minimum := AValue;
1503 Maximum := AValue;
1504end;
1505
1506function TDimensionMinMax.GetCenter: UInt32;
1507begin
1508 result := (Minimum+Maximum) shr 1;
1509end;
1510
1511procedure TDimensionMinMax.GrowToInclude(AValue: UInt32);
1512begin
1513 if AValue < Minimum then Minimum := AValue
1514 else if AValue > Maximum then Maximum := AValue;
1515end;
1516
1517{ TBGRAColorBox }
1518
1519function TBGRAColorBox.GetApparentInterval(ADimension: TColorDimension): UInt32;
1520var factor: single;
1521begin
1522 if not (ADimension in FDimensions) then result := 0
1523 else
1524 begin
1525 factor := 1;
1526 case ADimension of
1527 cdRGB: factor := 0.7;
1528 end;
1529 result := round(FBounds[ADimension].Size*factor);
1530 end;
1531end;
1532
1533function TBGRAColorBox.GetAverageColor: TBGRAPixel;
1534var
1535 n: integer;
1536 r, g, b, a: double;
1537 cura: double;
1538 w: UInt32;
1539begin
1540 a := 0;
1541 r := 0;
1542 g := 0;
1543 b := 0;
1544 w := 0;
1545 for n := 0 to high(FColors) do
1546 with FColors[n].Color do
1547 begin
1548 cura := (alpha / 255)*FColors[n].Weight;
1549 a += cura;
1550 r += GammaExpansionTab[red] * cura;
1551 g += GammaExpansionTab[green] * cura;
1552 b += GammaExpansionTab[blue] * cura;
1553 w += FColors[n].Weight;
1554 end;
1555 if w = 0 then
1556 Result := BGRAPixelTransparent
1557 else
1558 begin
1559 result.alpha := round(a*255/w);
1560 if result.alpha = 0 then result := BGRAPixelTransparent
1561 else
1562 begin
1563 result.red := GammaCompressionTab[round(r / a)];
1564 result.green := GammaCompressionTab[round(g / a)];
1565 result.blue := GammaCompressionTab[round(b / a)];
1566 end;
1567 end;
1568end;
1569
1570function TBGRAColorBox.GetAverageColorOrMainColor: TBGRAPixel;
1571var i: integer;
1572 maxWeight: UInt32;
1573begin
1574 result := BGRAPixelTransparent;
1575 maxWeight:= 0;
1576 for i := 0 to high(FColors) do
1577 with FColors[i] do
1578 begin
1579 if Weight > maxWeight then
1580 begin
1581 maxWeight:= Weight;
1582 result := Color;
1583 end;
1584 end;
1585 if maxWeight <= 3*FTotalWeight shr 2 then
1586 result := GetAverageColor;
1587end;
1588
1589function TBGRAColorBox.GetBounds(ADimension: TColorDimension): TDimensionMinMax;
1590begin
1591 result := FBounds[ADimension];
1592end;
1593
1594function TBGRAColorBox.GetColorCount(ACountPureTransparent: boolean): integer;
1595begin
1596 result := length(FColors);
1597 if ACountPureTransparent and HasPureTransparentColor then inc(result);
1598end;
1599
1600function TBGRAColorBox.GetHasPureTransparentColor: boolean;
1601begin
1602 result := FPureTransparentColorCount > 0;
1603end;
1604
1605function TBGRAColorBox.GetInferiorColor: TBGRAPixel;
1606var
1607 n: integer;
1608 r, g, b, a: double;
1609 w: UInt32;
1610 cura: double;
1611 wantedWeight: UInt32;
1612begin
1613 a := 0;
1614 r := 0;
1615 g := 0;
1616 b := 0;
1617 w := 0;
1618 wantedWeight:= FTotalWeight div 10;
1619 for n := 0 to high(FColors) do
1620 with FColors[n].Color do
1621 begin
1622 cura := (alpha / 255)*FColors[n].Weight;
1623 a += cura;
1624 r += red * cura;
1625 g += green * cura;
1626 b += blue * cura;
1627 w += FColors[n].Weight;
1628 if w >= wantedWeight then break;
1629 end;
1630 if w = 0 then
1631 Result := BGRAPixelTransparent
1632 else
1633 begin
1634 result.alpha := round(a*255/w);
1635 if result.alpha = 0 then result := BGRAPixelTransparent
1636 else
1637 begin
1638 result.red := round(r / a);
1639 result.green := round(g / a);
1640 result.blue := round(b / a);
1641 end;
1642 end;
1643end;
1644
1645function TBGRAColorBox.GetLargestApparentDimension: TColorDimension;
1646var c: TColorDimension;
1647 curApparentInterval, maxApparentInterval: UInt32;
1648begin
1649 c := succ(low(TColorDimension));
1650 result := c;
1651 maxApparentInterval:= ApparentInterval[c];
1652 while c < high(TColorDimension) do
1653 begin
1654 inc(c);
1655 curApparentInterval:= ApparentInterval[c];
1656 if curApparentInterval > maxApparentInterval then
1657 begin
1658 maxApparentInterval:= curApparentInterval;
1659 result := c;
1660 end;
1661 end;
1662end;
1663
1664function TBGRAColorBox.GetLargestApparentInterval: UInt32;
1665var
1666 curApparentInterval: UInt32;
1667 c: TColorDimension;
1668begin
1669 result:= ApparentInterval[succ(low(TColorDimension))];
1670 for c := succ(succ(low(TColorDimension))) to high(TColorDimension) do
1671 begin
1672 curApparentInterval:= ApparentInterval[c];
1673 if curApparentInterval > result then
1674 result := curApparentInterval;
1675 end;
1676end;
1677
1678function TBGRAColorBox.GetPointLike: boolean;
1679var c: TColorDimension;
1680begin
1681 for c := succ(low(TColorDimension)) to high(TColorDimension) do
1682 if not FBounds[c].PointLike then
1683 begin
1684 result := false;
1685 exit;
1686 end;
1687 result := true;
1688end;
1689
1690function TBGRAColorBox.GetSuperiorColor: TBGRAPixel;
1691var
1692 n: integer;
1693 r, g, b, a: double;
1694 w: UInt32;
1695 cura: double;
1696 wantedWeight: UInt32;
1697begin
1698 a := 0;
1699 r := 0;
1700 g := 0;
1701 b := 0;
1702 w := 0;
1703 wantedWeight:= FTotalWeight div 10;
1704 for n := high(FColors) downto 0 do
1705 with FColors[n].Color do
1706 begin
1707 cura := (alpha / 255)*FColors[n].Weight;
1708 a += cura;
1709 r += red * cura;
1710 g += green * cura;
1711 b += blue * cura;
1712 w += FColors[n].Weight;
1713 if w >= wantedWeight then break;
1714 end;
1715 if w = 0 then
1716 Result := BGRAPixelTransparent
1717 else
1718 begin
1719 result.alpha := round(a*255/w);
1720 if result.alpha = 0 then result := BGRAPixelTransparent
1721 else
1722 begin
1723 result.red := round(r / a);
1724 result.green := round(g / a);
1725 result.blue := round(b / a);
1726 end;
1727 end;
1728end;
1729
1730procedure TBGRAColorBox.Init(AColors: ArrayOfWeightedColor; AOwner: boolean);
1731var
1732 i,idx: NativeInt;
1733 FirstColor: boolean;
1734 c: TColorDimension;
1735begin
1736 FPureTransparentColorCount:= 0;
1737 FTotalWeight:= 0;
1738 for c := low(TColorDimension) to high(TColorDimension) do
1739 FBounds[c].SetAsPoint(0);
1740 FirstColor := True;
1741 if AOwner then
1742 FColors := AColors
1743 else
1744 SetLength(FColors, length(AColors));
1745 idx := 0;
1746 for i := 0 to high(AColors) do
1747 with AColors[i] do
1748 begin
1749 if Color.alpha > 0 then
1750 begin
1751 if FirstColor then
1752 begin
1753 for c := low(TColorDimension) to high(TColorDimension) do
1754 FBounds[c].SetAsPoint(GetDimensionValue(Color,c));
1755 FirstColor := false;
1756 end else
1757 begin
1758 for c := low(TColorDimension) to high(TColorDimension) do
1759 FBounds[c].GrowToInclude(GetDimensionValue(Color,c));
1760 end;
1761 inc(FTotalWeight, Weight);
1762 if not AOwner or (idx <> i) then
1763 FColors[idx] := AColors[i];
1764 inc(idx);
1765 end else
1766 inc(FPureTransparentColorCount, Weight);
1767 end;
1768 setlength(FColors,idx);
1769end;
1770
1771procedure TBGRAColorBox.SortBy(ADimension: TColorDimension);
1772var comparer: TIsChannelStrictlyGreaterFunc;
1773begin
1774 comparer := GetPixelStrictComparer(ADimension);
1775 if comparer = nil then exit;
1776 ArrayOfWeightedColor_QuickSort(FColors,0,high(FColors),comparer)
1777end;
1778
1779function TBGRAColorBox.GetMedianIndex(ADimension: TColorDimension;
1780 AMinValue, AMaxValue: UInt32
1781 ): integer;
1782var i: integer;
1783 sum,goal: UInt32;
1784 valueComparer: TIsChannelGreaterThanOrEqualToValueFunc;
1785 strictComparer: TIsChannelStrictlyGreaterFunc;
1786 ofs: integer;
1787begin
1788 if length(FColors) = 1 then
1789 begin
1790 result := 0;
1791 exit;
1792 end else
1793 if length(FColors) = 0 then
1794 begin
1795 result := -1;
1796 exit;
1797 end;
1798 valueComparer:= GetPixelValueComparer(ADimension);
1799 sum := 0;
1800 goal := (FTotalWeight+1) shr 1;
1801 result := high(FColors) shr 1;
1802 for i := 0 to high(FColors) do
1803 begin
1804 inc(sum, FColors[i].Weight);
1805 if (sum>=goal) and (valueComparer(@FColors[i].Color, AMinValue)) then
1806 begin
1807 result := i;
1808 while (result > 0) and (valueComparer(@FColors[result].Color, AMaxValue+1)) do dec(result);
1809 break;
1810 end;
1811 end;
1812 if result = 0 then inc(result);
1813 //check that there it is not splitting consecutive colors with the same value
1814 strictComparer := GetPixelStrictComparer(ADimension);
1815 ofs := 0;
1816 while true do
1817 begin
1818 if (result-ofs < 1) and (result+ofs > high(FColors)) then break;
1819 if (result-ofs >= 1) and strictComparer(@FColors[result-ofs].Color,@FColors[result-ofs-1].Color) then
1820 begin
1821 result := result-ofs;
1822 exit;
1823 end;
1824 if (result+ofs <= high(FColors)) and strictComparer(@FColors[result+ofs].Color,@FColors[result+ofs-1].Color) then
1825 begin
1826 result := result+ofs;
1827 exit;
1828 end;
1829 inc(ofs);
1830 end;
1831end;
1832
1833constructor TBGRAColorBox.Create(ADimensions: TColorDimensions; AColors: ArrayOfWeightedColor; AOwner: boolean);
1834begin
1835 FDimensions:= ADimensions;
1836 Init(AColors,AOwner);
1837end;
1838
1839constructor TBGRAColorBox.Create(ADimensions: TColorDimensions;
1840 const AColors: ArrayOfTBGRAPixel; AAlpha: TAlphaChannelPaletteOption = acFullChannelInPalette);
1841var weightedColors: ArrayOfWeightedColor;
1842 i: Integer;
1843begin
1844 if AAlpha = acFullChannelInPalette then
1845 begin
1846 FDimensions:= ADimensions;
1847 setlength(weightedColors, length(AColors));
1848 for i := 0 to high(weightedColors) do
1849 with weightedColors[i] do
1850 begin
1851 color := AColors[i];
1852 Weight:= 1;
1853 end;
1854 Init(weightedColors,True);
1855 end else
1856 Create(ADimensions, @AColors[0], length(AColors), AAlpha);
1857end;
1858
1859constructor TBGRAColorBox.Create(ADimensions: TColorDimensions; ABounds: TColorBoxBounds);
1860begin
1861 FDimensions:= ADimensions;
1862 FBounds := ABounds;
1863 FTotalWeight:= 0;
1864 FPureTransparentColorCount:= 0;
1865end;
1866
1867constructor TBGRAColorBox.Create(ADimensions: TColorDimensions; APalette: TBGRACustomPalette);
1868begin
1869 FDimensions:= ADimensions;
1870 Init(APalette.GetAsArrayOfWeightedColor,False);
1871end;
1872
1873constructor TBGRAColorBox.Create(ADimensions: TColorDimensions;
1874 ABitmap: TBGRACustomBitmap; AAlpha: TAlphaChannelPaletteOption);
1875begin
1876 Create(ADimensions, ABitmap.Data, ABitmap.NbPixels, AAlpha);
1877end;
1878
1879constructor TBGRAColorBox.Create(ADimensions: TColorDimensions; AColors: PBGRAPixel; ANbPixels: integer; AAlpha: TAlphaChannelPaletteOption);
1880var i,j,prev,idx: integer;
1881 p: PBGRAPixel;
1882 skip: boolean;
1883 alphaMask: DWord;
1884 transpIndex: integer;
1885begin
1886 if AAlpha <> acFullChannelInPalette then
1887 alphaMask := LEtoN($FF000000)
1888 else
1889 alphaMask := 0;
1890 FDimensions:= ADimensions;
1891 transpIndex := -1;
1892 SetLength(FColors,ANbPixels);
1893 if length(FColors)>0 then
1894 begin
1895 p := AColors;
1896 idx := 0;
1897 for i := 0 to ANbPixels-1 do
1898 begin
1899 if (p^.alpha = 0) or ((AAlpha = acTransparentEntry) and (p^.alpha < 128)) then
1900 begin
1901 skip := true;
1902 if not (AAlpha = acIgnore) then
1903 begin
1904 if (transpIndex=-1) then
1905 begin
1906 transpIndex := idx;
1907 with FColors[idx] do
1908 begin
1909 Color := BGRAPixelTransparent;
1910 Weight:= 1;
1911 end;
1912 inc(idx);
1913 end else
1914 inc(FColors[transpIndex].Weight);
1915 end;
1916 if (p^.alpha = 0) then
1917 begin
1918 inc(p);
1919 continue;
1920 end;
1921 end;
1922 skip := false;
1923 for j := idx-1 downto idx-10 do
1924 if j < 0 then
1925 break
1926 else
1927 with FColors[j] do
1928 if DWord(Color)=DWord(p^) or alphaMask then
1929 begin
1930 skip := true;
1931 inc(Weight);
1932 break;
1933 end;
1934 if skip then
1935 begin
1936 inc(p);
1937 continue;
1938 end;
1939 with FColors[idx] do
1940 begin
1941 Color := p^;
1942 if AAlpha <> acFullChannelInPalette then Color.alpha := 255;
1943 Weight := 1;
1944 inc(p);
1945 inc(idx);
1946 end;
1947 end;
1948 setLength(FColors, idx);
1949
1950 ArrayOfWeightedColor_QuickSort(FColors,0,high(FColors),@IsDWordGreater);
1951 prev := 0;
1952 for i := 1 to high(FColors) do
1953 begin
1954 if DWord(FColors[i].Color)=DWord(FColors[prev].Color) then
1955 inc(FColors[prev].Weight, FColors[i].Weight)
1956 else
1957 begin
1958 inc(prev);
1959 if i <> prev then
1960 FColors[prev] := FColors[i];
1961 end;
1962 end;
1963 setlength(FColors, prev+1);
1964 end;
1965 Init(FColors,True);
1966end;
1967
1968function TBGRAColorBox.BoundsContain(AColor: TBGRAPixel): boolean;
1969var c: TColorDimension;
1970begin
1971 for c := succ(low(TColorDimension)) to high(TColorDimension) do
1972 if not FBounds[c].Contains(GetDimensionValue(AColor,c)) then
1973 begin
1974 result := false;
1975 exit;
1976 end;
1977 result := true;
1978end;
1979
1980function TBGRAColorBox.MedianCut(ADimension: TColorDimension; out SuperiorMiddle: UInt32
1981 ): TBGRAColorBox;
1982var idxSplit: NativeInt;
1983 secondArray: ArrayOfWeightedColor;
1984 i: NativeInt;
1985begin
1986 result := nil;
1987 SuperiorMiddle := 0;
1988 if FBounds[ADimension].PointLike then exit;
1989 if length(FColors) <= 1 then exit;
1990 SortBy(ADimension);
1991 idxSplit := GetMedianIndex(ADimension,
1992 round(FBounds[ADimension].Minimum*(1-MedianMinPercentage)+FBounds[ADimension].Maximum*MedianMinPercentage),
1993 round(FBounds[ADimension].Minimum*MedianMinPercentage+FBounds[ADimension].Maximum*(1-MedianMinPercentage)));
1994 if idxSplit = -1 then exit;
1995 setlength(secondArray, length(FColors)-idxSplit);
1996 for i := idxSplit to high(FColors) do
1997 secondArray[i-idxSplit] := FColors[i];
1998 result := TBGRAColorBox.Create(FDimensions, secondArray,True);
1999 setlength(FColors, idxSplit);
2000 Init(FColors,True);
2001 SuperiorMiddle := (FBounds[ADimension].Maximum + result.FBounds[ADimension].Minimum + 1) shr 1;
2002end;
2003
2004function TBGRAColorBox.Duplicate: TBGRAColorBox;
2005var
2006 i: NativeInt;
2007begin
2008 result := TBGRAColorBox.Create(FDimensions, FBounds);
2009 result.FTotalWeight := FTotalWeight;
2010 setlength(result.FColors, length(FColors));
2011 for i := 0 to high(FColors) do
2012 result.FColors[i] := FColors[i];
2013end;
2014
2015function TBGRAColorBox.GetAsArrayOfColors(AIncludePureTransparent: boolean): ArrayOfTBGRAPixel;
2016var i,idx: integer;
2017begin
2018 if AIncludePureTransparent and HasPureTransparentColor then
2019 begin
2020 setlength(result, length(FColors)+1);
2021 result[0] := BGRAPixelTransparent;
2022 idx := 1;
2023 end else
2024 begin
2025 setlength(result, length(FColors));
2026 idx := 0;
2027 end;
2028 for i:= 0 to high(FColors) do
2029 begin
2030 result[idx] := FColors[i].Color;
2031 inc(idx);
2032 end;
2033end;
2034
2035end.
2036
Note: See TracBrowser for help on using the repository browser.