Is your feature request related to a problem? Please describe
I am trying to remove references to System.Drawing.Bitmap in my own code and replace them with usages of MagickImage for purposes of resolving CA1416
This particular pattern is proving difficult to replicate in a performant way.
// Given:
// int width, height;
// Color[] palette;
// int[] indexData (width * height)
var bmp = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
var p = bmp.Palette;
for (var i = 0; i < palette.Length; i++)
{
p.Entries[i] = palette[i];
}
bmp.Palette = p;
var bmpData = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
var scan = bmpData.Scan0;
for (var y = 0; y < bmpData.Height; y++, scan += bmpData.Stride)
{
Marshal.Copy(indexData, y * width, scan, bmpData.Width);
}
bmp.UnlockBits(bmpData);
Per #1828, it was revealed that there is no quick way to create an indexed image like this.
Describe the solution you'd like
Personally, I think the API should look like this:
//uint width, height;
//MagickColor[] palette;
//int[] indexData (width * height)
var bmp = new MagickImage(width: width, height: height, colormapSize: 1 << 8);
for (var i = 0; i < palette.Length; i++)
{
bmp.SetColormapColor(i, palette[i]);
}
bmp.ImportPixels(indexData, new PixelImportSettings(width, height, StorageType.Int32, "I"));
See "Additional context" below for why this may not work directly. Perhaps the base library could be augmented to support an extra X map for inidices.
Describe alternatives you've considered
Alternatives:
- Format the memory as a TGA file and load.
- OK, but not ergonomic. May require large memory copies.
- Load RGBA data and quantize afterwards.
- Fails some uses cases. e.g If a palette swap mapped two entries to the same RGB value, then the indexes could not be recovered.
Additional context
If we look at the underlying library we can see the following patterns for the API:
GIF:
q=QueueAuthenticPixels(image,0,offset,image->columns,1,exception);
if (q == (Quantum *) NULL)
break;
for (x=0; x < (ssize_t) image->columns; )
{
c=ReadBlobLZWByte(lzw_info);
if (c < 0)
break;
index=ConstrainColormapIndex(image,(ssize_t) c,exception);
SetPixelIndex(image,(Quantum) index,q);
SetPixelViaPixelInfo(image,image->colormap+index,q);
SetPixelAlpha(image,index == opacity ? TransparentAlpha : OpaqueAlpha,q);
x++;
q+=(ptrdiff_t) GetPixelChannels(image);
}
PCX:
for (x=0; x < (ssize_t) image->columns; x++)
{
if (image->storage_class == PseudoClass)
SetPixelIndex(image,*r++,q);
else
{
SetPixelRed(image,ScaleCharToQuantum(*r++),q);
SetPixelGreen(image,ScaleCharToQuantum(*r++),q);
SetPixelBlue(image,ScaleCharToQuantum(*r++),q);
if (image->alpha_trait != UndefinedPixelTrait)
SetPixelAlpha(image,ScaleCharToQuantum(*r++),q);
}
q+=(ptrdiff_t) GetPixelChannels(image);
}
PNG:
q=GetAuthenticPixels(image,0,y,image->columns,1,exception);
if (q == (Quantum *) NULL)
break;
for (x=0; x < (ssize_t) image->columns; x++)
{
ssize_t index=ConstrainColormapIndex(image,(ssize_t) *r,exception);
SetPixelRed(image,ClampToQuantum(image->colormap[index].red),q);
SetPixelGreen(image,ClampToQuantum(image->colormap[index].green),q);
SetPixelBlue(image,ClampToQuantum(image->colormap[index].blue),q);
SetPixelIndex(image,(Quantum) index,q);
r++;
q+=(ptrdiff_t) GetPixelChannels(image);
}
TGA:
q=QueueAuthenticPixels(image,0,y_offset,image->columns,1,exception);
if (q == (Quantum *) NULL)
break;
if (flip_x != MagickFalse)
q+=(ptrdiff_t) GetPixelChannels(image)*(image->columns-1);
for (x=0; x < (ssize_t) image->columns; x++)
{
...
if (status == MagickFalse)
ThrowReaderException(CorruptImageError,"UnableToReadImageData");
if (image->storage_class == PseudoClass)
SetPixelIndex(image,index,q);
SetPixelRed(image,ClampToQuantum(pixel.red),q);
SetPixelGreen(image,ClampToQuantum(pixel.green),q);
SetPixelBlue(image,ClampToQuantum(pixel.blue),q);
if (image->alpha_trait != UndefinedPixelTrait)
SetPixelAlpha(image,ClampToQuantum(pixel.alpha),q);
if (flip_x != MagickFalse)
q-=GetPixelChannels(image);
else
q+=(ptrdiff_t) GetPixelChannels(image);
}
However,
ImportImagePixels sets the colorspace to gray when using "I":
case 'I':
case 'i':
{
quantum_map[i]=IndexQuantum;
(void) SetImageColorspace(image,GRAYColorspace,exception);
break;
}
While the ImportCharPixels family of functions use SetPixelGray and never SetPixelIndex.
if (LocaleCompare(map,"I") == 0)
{
for (y=0; y < (ssize_t) roi->height; y++)
{
q=GetAuthenticPixels(image,roi->x,roi->y+y,roi->width,1,exception);
if (q == (Quantum *) NULL)
break;
for (x=0; x < (ssize_t) roi->width; x++)
{
SetPixelGray(image,ScaleCharToQuantum(*p++),q);
q+=(ptrdiff_t) GetPixelChannels(image);
}
if (SyncAuthenticPixels(image,exception) == MagickFalse)
break;
}
return(y < (ssize_t) roi->height ? MagickFalse : MagickTrue);
}
Is your feature request related to a problem? Please describe
I am trying to remove references to System.Drawing.Bitmap in my own code and replace them with usages of MagickImage for purposes of resolving CA1416
This particular pattern is proving difficult to replicate in a performant way.
Per #1828, it was revealed that there is no quick way to create an indexed image like this.
Describe the solution you'd like
Personally, I think the API should look like this:
See "Additional context" below for why this may not work directly. Perhaps the base library could be augmented to support an extra
Xmap for inidices.Describe alternatives you've considered
Alternatives:
Additional context
If we look at the underlying library we can see the following patterns for the API:
GIF:
PCX:
PNG:
TGA:
However,
ImportImagePixels sets the colorspace to gray when using "I":
While the ImportCharPixels family of functions use
SetPixelGrayand neverSetPixelIndex.