Commit a4a49d293 for imagemagick.org
commit a4a49d29361063969c1827ac77194fa44c8464fd
Author: Artem Lytkin <146867384+4RH1T3CT0R7@users.noreply.github.com>
Date: Sun Sep 27 16:25:24 2026 +0300
Use a hash table to find palette entries when writing PNG (#8977)
BUILD_PALETTE looked up every pixel in the opaque, transparent and
semitransparent lists with a linear scan, and then scanned the new
colormap again for every pixel to set its index. For an 8-bit gray or
256 color image that is about 128 compares per pixel in each pass.
Both lookups now probe a small open-addressing table keyed on the
8-bit scaled channels. Every hit is still confirmed with the original
comparison, so the palette, its order and the indexes are unchanged.
diff --git a/coders/png.c b/coders/png.c
index 93b928865..04e1c7222 100644
--- a/coders/png.c
+++ b/coders/png.c
@@ -7816,6 +7816,26 @@ static inline MagickBooleanType IsColorEqual(const Image *image,
return(MagickFalse);
}
+#define PNGPaletteHashSize 1024
+
+static inline size_t PNGPaletteHash(const Quantum red,const Quantum green,
+ const Quantum blue,const Quantum alpha)
+{
+ size_t
+ hash;
+
+ /*
+ Colors that compare equal share a hash (barring HDRI values within
+ MagickEpsilon of a rounding edge), so a probe that stops at the first
+ empty slot finds them.
+ */
+ hash=(size_t) ScaleQuantumToChar(red);
+ hash=31*hash+(size_t) ScaleQuantumToChar(green);
+ hash=31*hash+(size_t) ScaleQuantumToChar(blue);
+ hash=31*hash+(size_t) ScaleQuantumToChar(alpha);
+ return(hash & (PNGPaletteHashSize-1));
+}
+
#if defined(PNG_tIME_SUPPORTED)
static void write_tIME_chunk(Image *image,png_struct *ping,png_info *info,
const char *timestamp,ExceptionInfo *exception)
@@ -8539,6 +8559,14 @@ static MagickBooleanType WriteOnePNGImage(MngWriteInfo *mng_info,
Quantum
*q;
+ size_t
+ h;
+
+ unsigned short
+ opaque_hash[PNGPaletteHashSize],
+ semitransparent_hash[PNGPaletteHashSize],
+ transparent_hash[PNGPaletteHashSize];
+
if (logging != MagickFalse)
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
" Enter BUILD_PALETTE:");
@@ -8603,6 +8631,9 @@ static MagickBooleanType WriteOnePNGImage(MngWriteInfo *mng_info,
number_opaque = 0;
number_semitransparent = 0;
number_transparent = 0;
+ (void) memset(opaque_hash,0,sizeof(opaque_hash));
+ (void) memset(semitransparent_hash,0,sizeof(semitransparent_hash));
+ (void) memset(transparent_hash,0,sizeof(transparent_hash));
for (y=0; y < (ssize_t) image->rows; y++)
{
@@ -8619,24 +8650,27 @@ static MagickBooleanType WriteOnePNGImage(MngWriteInfo *mng_info,
{
if (number_opaque < 259)
{
+ h=PNGPaletteHash(GetPixelRed(image,r),GetPixelGreen(image,r),
+ GetPixelBlue(image,r),(Quantum) 0);
if (number_opaque == 0)
{
GetPixelInfoPixel(image,r,opaque);
opaque[0].alpha=OpaqueAlpha;
number_opaque=1;
+ opaque_hash[h]=1;
}
- for (i=0; i< (ssize_t) number_opaque; i++)
- {
- if (IsColorEqual(image,r,opaque+i))
- break;
- }
+ while ((opaque_hash[h] != 0) &&
+ (IsColorEqual(image,r,opaque+opaque_hash[h]-1) ==
+ MagickFalse))
+ h=(h+1) & (PNGPaletteHashSize-1);
- if (i == (ssize_t) number_opaque && number_opaque < 259)
+ if (opaque_hash[h] == 0)
{
- number_opaque++;
+ i=(ssize_t) number_opaque++;
GetPixelInfoPixel(image,r,opaque+i);
opaque[i].alpha=OpaqueAlpha;
+ opaque_hash[h]=(unsigned short) number_opaque;
}
}
}
@@ -8644,6 +8678,8 @@ static MagickBooleanType WriteOnePNGImage(MngWriteInfo *mng_info,
{
if (number_transparent < 259)
{
+ h=PNGPaletteHash(GetPixelRed(image,r),GetPixelGreen(image,r),
+ GetPixelBlue(image,r),(Quantum) 0);
if (number_transparent == 0)
{
GetPixelInfoPixel(image,r,transparent);
@@ -8656,19 +8692,19 @@ static MagickBooleanType WriteOnePNGImage(MngWriteInfo *mng_info,
ping_trans_color.gray=(unsigned short)
GetPixelGray(image,r);
number_transparent = 1;
+ transparent_hash[h]=1;
}
- for (i=0; i< (ssize_t) number_transparent; i++)
- {
- if (IsColorEqual(image,r,transparent+i))
- break;
- }
+ while ((transparent_hash[h] != 0) &&
+ (IsColorEqual(image,r,transparent+
+ transparent_hash[h]-1) == MagickFalse))
+ h=(h+1) & (PNGPaletteHashSize-1);
- if (i == (ssize_t) number_transparent &&
- number_transparent < 259)
+ if (transparent_hash[h] == 0)
{
- number_transparent++;
+ i=(ssize_t) number_transparent++;
GetPixelInfoPixel(image,r,transparent+i);
+ transparent_hash[h]=(unsigned short) number_transparent;
}
}
}
@@ -8676,25 +8712,31 @@ static MagickBooleanType WriteOnePNGImage(MngWriteInfo *mng_info,
{
if (number_semitransparent < 259)
{
+ h=PNGPaletteHash(GetPixelRed(image,r),GetPixelGreen(image,r),
+ GetPixelBlue(image,r),GetPixelAlpha(image,r));
if (number_semitransparent == 0)
{
GetPixelInfoPixel(image,r,semitransparent);
number_semitransparent = 1;
+ semitransparent_hash[h]=1;
}
- for (i=0; i< (ssize_t) number_semitransparent; i++)
+ while (semitransparent_hash[h] != 0)
{
+ i=(ssize_t) semitransparent_hash[h]-1;
if (IsColorEqual(image,r,semitransparent+i)
&& (double) GetPixelAlpha(image,r) ==
semitransparent[i].alpha)
break;
+ h=(h+1) & (PNGPaletteHashSize-1);
}
- if (i == (ssize_t) number_semitransparent &&
- number_semitransparent < 259)
+ if (semitransparent_hash[h] == 0)
{
- number_semitransparent++;
+ i=(ssize_t) number_semitransparent++;
GetPixelInfoPixel(image,r,semitransparent+i);
+ semitransparent_hash[h]=(unsigned short)
+ number_semitransparent;
}
}
}
@@ -8795,9 +8837,15 @@ static MagickBooleanType WriteOnePNGImage(MngWriteInfo *mng_info,
if (image_colors < 257)
{
+ MagickBooleanType
+ ignore_alpha;
+
PixelInfo
colormap[260];
+ unsigned short
+ colormap_hash[PNGPaletteHashSize];
+
/*
* Initialize image colormap.
*/
@@ -8886,6 +8934,21 @@ static MagickBooleanType WriteOnePNGImage(MngWriteInfo *mng_info,
/* Sync the pixel indices with the new colormap */
+ ignore_alpha=(((image->alpha_trait & BlendPixelTrait) == 0) &&
+ ((image->alpha_trait & UpdatePixelTrait) == 0)) ? MagickTrue :
+ MagickFalse;
+ (void) memset(colormap_hash,0,sizeof(colormap_hash));
+ for (i=0; i< (ssize_t) image_colors; i++)
+ {
+ h=PNGPaletteHash((Quantum) image->colormap[i].red,
+ (Quantum) image->colormap[i].green,
+ (Quantum) image->colormap[i].blue,ignore_alpha != MagickFalse ?
+ (Quantum) 0 : (Quantum) image->colormap[i].alpha);
+ while (colormap_hash[h] != 0)
+ h=(h+1) & (PNGPaletteHashSize-1);
+ colormap_hash[h]=(unsigned short) (i+1);
+ }
+
for (y=0; y < (ssize_t) image->rows; y++)
{
q=GetAuthenticPixels(image,0,y,image->columns,1,exception);
@@ -8895,10 +8958,13 @@ static MagickBooleanType WriteOnePNGImage(MngWriteInfo *mng_info,
for (x=0; x < (ssize_t) image->columns; x++)
{
- for (i=0; i< (ssize_t) image_colors; i++)
+ h=PNGPaletteHash(GetPixelRed(image,q),GetPixelGreen(image,q),
+ GetPixelBlue(image,q),ignore_alpha != MagickFalse ?
+ (Quantum) 0 : GetPixelAlpha(image,q));
+ while (colormap_hash[h] != 0)
{
- if (((((image->alpha_trait & BlendPixelTrait) == 0) &&
- (((image->alpha_trait & UpdatePixelTrait) == 0))) ||
+ i=(ssize_t) colormap_hash[h]-1;
+ if ((ignore_alpha != MagickFalse ||
image->colormap[i].alpha == (double) GetPixelAlpha(image,q)) &&
image->colormap[i].red == (double) GetPixelRed(image,q) &&
image->colormap[i].green == (double) GetPixelGreen(image,q) &&
@@ -8907,6 +8973,7 @@ static MagickBooleanType WriteOnePNGImage(MngWriteInfo *mng_info,
SetPixelIndex(image,(Quantum) i,q);
break;
}
+ h=(h+1) & (PNGPaletteHashSize-1);
}
q+=(ptrdiff_t) GetPixelChannels(image);
}