Error in Quantization of Image using NQUANT - c#

I recently acquired the NuGet Package Nquant.
I plan to use this to reduce the file size of the bitmap and save it into PNG. But I get this error:
The image you are attempting to quantize does not contain a 32 bit ARGB palette. This image has a bit depth of 8 with 256 colors.
Does anyone here has used Nquant? And have you encountered this error and how did you fix it?
My code for your reference:
var bitmap = new Bitmap(width, jbgsize / height, PixelFormat.Format8bppIndexed);
ColorPalette pal = bitmap.Palette;
for (int i = 0; i <= 255; i++)
{
// create greyscale color table
pal.Entries[i] = Color.FromArgb(i, i, i);
}
bitmap.Palette = pal; // you need to re-set this property to force the new ColorPalette
var bitmap_data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
Marshal.Copy(output, 0, bitmap_data.Scan0, output.Length);
bitmap.UnlockBits(bitmap_data);
MemoryStream stream = new MemoryStream();
var quantizer = new WuQuantizer();
using(var bmp = new Bitmap(bitmap))
{
using (var quantized = quantizer.QuantizeImage(bitmap))
{
quantized.Save(stream, ImageFormat.Png);
}
}
byteArray = stream.ToArray();
return byteArray.Concat(output).ToArray();

You can convert your image to Format32bppPArgb and then Quantize it.
This is my working example of that decrease image size for ~3 times.
public static byte[] CompressImageStream(byte[] imageStream)
{
using (var ms = new MemoryStream(imageStream))
using (var original = new Bitmap(ms))
using (var clonedWith32PixelsFormat = new Bitmap(
original.Width,
original.Height,
PixelFormat.Format32bppPArgb))
{
using (Graphics gr = Graphics.FromImage(clonedWith32PixelsFormat))
{
gr.DrawImage(
original,
new Rectangle(0, 0, clonedWith32PixelsFormat.Width, clonedWith32PixelsFormat.Height));
}
using (Image compressedImage = new WuQuantizer().QuantizeImage(clonedWith32PixelsFormat))
{
return ImageToByteArray(compressedImage);
}
}
}
public static byte[] ImageToByteArray(Image image)
{
if (image == null)
{
throw new ArgumentNullException(nameof(image));
}
using (var stream = new MemoryStream())
{
image.Save(stream, ImageFormat.Png);
return stream.ToArray();
}
}

Related

Font size of graphics.DrawString on an image does not same with result image

I'm working on a task that draw a string on an image.
but the result image is not same with source code that I wrote down.
following image shows the font size difference.
red text is written in NanumSquare font 18px in window paint.
and the black text date below red text is also NanumSquare font 18px. It is written with C# source code.
following is my source code. C#.
static void Main(string[] args)
{
DrawTextToImageSave("webPrint_back.png");
}
public static void DrawTextToImageSave(string path)
{
//png to bitmap
Image Dummy = Image.FromFile(path);
using (Bitmap bitmap = (Bitmap)Dummy)
{//load the image file
using (Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
var titleFont = new Font("NanumSquareOTF ExtraBold", 25);
var bodyFont = new Font("NanumSquareOTF Regular", 25);
graphics.DrawString("DATE", titleFont, System.Drawing.Brushes.Black, new PointF(401.5f, 863.5f)); //comment 1
graphics.DrawString(DateTime.Now.ToString("yyyy.MM.dd"), bodyFont, System.Drawing.Brushes.Black, new PointF(345, 885f));
graphics.DrawString("LOCATION", titleFont, System.Drawing.Brushes.Black, new PointF(344, 919.5f));
graphics.DrawString(System.DateTime.Now.ToString("yyyyMMddHHmmss") , bodyFont, System.Drawing.Brushes.Black, new PointF(267f, 946f));
WriteableBitmap bitmapimg = Generator128Code("STACKOVERFLOW", 110, 110);
bitmapimg = resize_image(bitmapimg, 1.4); //comment 2
var stream = new MemoryStream();
var encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmapimg));
encoder.Save(stream);
byte[] buffer = stream.GetBuffer();
var qrBitmap = new System.Drawing.Bitmap(new MemoryStream(buffer));
graphics.DrawImage(qrBitmap, 485f, 855f);
}
bitmap.Save( "output_WebPrintBack.png", ImageFormat.Png);
}
}
see comment 1. I expect it draws exactly 18px font. but It does not.
I have also same problem on drawing qrcode with zxing.
without comment2 code I get a ~90 px qr code size.
public static WriteableBitmap Generator128Code(string contents, int width, int height)
{
if (string.IsNullOrEmpty(contents))
{
return null;
}
EncodingOptions options = null;
BarcodeWriter writer = null;
options = new QrCodeEncodingOptions
{
CharacterSet = "UTF-8",
Width = width,
Height = height,
ErrorCorrection = ErrorCorrectionLevel.H,
Margin = 0
};
writer = new BarcodeWriter
{
Format = BarcodeFormat.QR_CODE,
Options = options
};
WriteableBitmap bitmap = writer.Write(contents);
return bitmap;
}
static WriteableBitmap resize_image(WriteableBitmap img, double scale)
{
BitmapSource source = img;
var s = new ScaleTransform(scale, scale);
var res = new TransformedBitmap(img, s);
return convert_BitmapSource_to_WriteableBitmap(res);
}
static WriteableBitmap convert_BitmapSource_to_WriteableBitmap(BitmapSource source)
{
// Calculate stride of source
int stride = source.PixelWidth * (source.Format.BitsPerPixel / 8);
// Create data array to hold source pixel data
byte[] data = new byte[stride * source.PixelHeight];
// Copy source image pixels to the data array
source.CopyPixels(data, stride, 0);
// Create WriteableBitmap to copy the pixel data to.
WriteableBitmap target = new WriteableBitmap(source.PixelWidth
, source.PixelHeight, source.DpiX, source.DpiY
, source.Format, null);
// Write the pixel data to the WriteableBitmap.
target.WritePixels(new Int32Rect(0, 0
, source.PixelWidth, source.PixelHeight)
, data, stride, 0);
return target;
}
with multiplying x 1.4 with its size, I can get similar result that I want.
why this difference has occured?
size of origin image is also 638px * 1010px. following image is origin image.
thank you for reading. and I apologize my poor English skill.
Edit
following source is executable with Console .net framework.
I retry with this source code but the result was same. :( ...
following source code is full source code.
you need png file that named "webPrint_back.png". and size 638x1010 . https://dummyimage.com/
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Text;
using System.IO;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using ZXing;
using ZXing.Common;
using ZXing.QrCode;
using ZXing.QrCode.Internal;
using BarcodeWriter = ZXing.Presentation.BarcodeWriter;
namespace bitmapTest
{
class Program
{
static void Main(string[] args)
{
DrawTextToImageSave("webPrint_back.png");
}
public static void DrawTextToImageSave(string path)
{
//png to bitmap
Image Dummy = Image.FromFile(path);
using (Bitmap bitmap = (Bitmap)Dummy)
{//load the image file
using (Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
var titleFont = new Font("NanumSquareOTF ExtraBold", 18);
var bodyFont = new Font("NanumSquareOTF Regular", 18);
graphics.DrawString("DATE", titleFont, System.Drawing.Brushes.Black, new PointF(401.5f, 863.5f)); //comment 1
graphics.DrawString(DateTime.Now.ToString("yyyy.MM.dd"), bodyFont, System.Drawing.Brushes.Black, new PointF(345, 885f));
graphics.DrawString("LOCATION", titleFont, System.Drawing.Brushes.Black, new PointF(344, 919.5f));
graphics.DrawString(System.DateTime.Now.ToString("yyyyMMddHHmmss") , bodyFont, System.Drawing.Brushes.Black, new PointF(267f, 946f));
WriteableBitmap bitmapimg = Generator128Code("STACKOVERFLOW", 110, 110);
bitmapimg = resize_image(bitmapimg, 1.4); //comment 2
var stream = new MemoryStream();
var encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmapimg));
encoder.Save(stream);
byte[] buffer = stream.GetBuffer();
var qrBitmap = new System.Drawing.Bitmap(new MemoryStream(buffer));
graphics.DrawImage(qrBitmap, 485f, 855f);
}
bitmap.Save( "output_WebPrintBack.png", ImageFormat.Png);
}
}
public static WriteableBitmap Generator128Code(string contents, int width, int height)
{
if (string.IsNullOrEmpty(contents))
{
return null;
}
EncodingOptions options = null;
BarcodeWriter writer = null;
options = new QrCodeEncodingOptions
{
CharacterSet = "UTF-8",
Width = width,
Height = height,
ErrorCorrection = ErrorCorrectionLevel.H,
Margin = 0
};
writer = new BarcodeWriter
{
Format = BarcodeFormat.QR_CODE,
Options = options
};
WriteableBitmap bitmap = writer.Write(contents);
return bitmap;
}
static WriteableBitmap resize_image(WriteableBitmap img, double scale)
{
BitmapSource source = img;
var s = new ScaleTransform(scale, scale);
var res = new TransformedBitmap(img, s);
return convert_BitmapSource_to_WriteableBitmap(res);
}
static WriteableBitmap convert_BitmapSource_to_WriteableBitmap(BitmapSource source)
{
// Calculate stride of source
int stride = source.PixelWidth * (source.Format.BitsPerPixel / 8);
// Create data array to hold source pixel data
byte[] data = new byte[stride * source.PixelHeight];
// Copy source image pixels to the data array
source.CopyPixels(data, stride, 0);
// Create WriteableBitmap to copy the pixel data to.
WriteableBitmap target = new WriteableBitmap(source.PixelWidth
, source.PixelHeight, source.DpiX, source.DpiY
, source.Format, null);
// Write the pixel data to the WriteableBitmap.
target.WritePixels(new Int32Rect(0, 0
, source.PixelWidth, source.PixelHeight)
, data, stride, 0);
return target;
}
}
}
I found the factor that makes font size is smaller than I expected.
First, source code that I posted works bad with PNG File with "24 bit depth"(without transparent background).
but PNG file with "32 bit depth"(with transparent background), Fontsize works well.
I don't know why It happened.
Second, the barcode made with zxing.net nuget. internally has padding with its border. the problem is that padding size depends on string length. longer string length makes more smaller barcode size and more bigger padding.
following source is my solution for barcode zxing.net nuget
WriteableBitmap bitmapimg = Generator128Code(StaticCommon.localConfigModel.cardBack_QRText, 110, 110);
var stream = new MemoryStream();
var encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmapimg));
encoder.Save(stream);
byte[] buffer = stream.GetBuffer();
var qrBitmap = new System.Drawing.Bitmap(new MemoryStream(buffer));
RectangleF recF = new RectangleF(new PointF(477f, 852f), new SizeF(130, 130));
//ZXING PADDING value, padding size depends on QR encoded string length, so I divide with integer 30 and use remainder
int len = StaticCommon.localConfigModel.cardBack_QRText.Length;
int pad = (int)len / 30;
if (len % 30 > 0) pad++;
RectangleF srecF = new RectangleF(pad * 6f, pad * 6f, 110f - pad * 12f, 110 - pad * 12f);
graphics.DrawImage(qrBitmap, recF, srecF, GraphicsUnit.Pixel);
I solved my problem with engineering way, but I hope someone solve this problem with theoretical way. so I remain this question unsolved.

GIF image cropping in c# without add-ons

I'm trying to crop gif images in c# without using an add-ons with drawing a rectangle on the image (and I've got it to work with normal image), but .. lets just say the output is less than optimal..
examples:
Origianl Image:
Cropped Image:
and here's the code that I'm using :
public override Image Crop(Rectangle f, bool isStrch, int slashwid, int slashhei)
{
_revert = Image;
GifDecoder();
widmult = Width / (double)slashwid;
heimult = Height / (double)slashhei;
double num = (double)f.X * widmult;
double num2 = (double)f.Y * heimult;
double num3 = (double)f.Width * widmult;
double num4 = (double)f.Height * heimult;
Rectangle srcRect = new Rectangle(isStrch ? ((int)num) : f.X, isStrch ? ((int)num2) : f.Y, isStrch ? ((int)num3) : f.Width, isStrch ? ((int)num4) : f.Height);
CroppedFrames = new Image[Frames.Length];
for (int i = 0; i < Frames.Length; i++)
{
Bitmap image = new Bitmap(Frames[i]);
Bitmap bitmap = new Bitmap(srcRect.Width, srcRect.Height);
using (Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.DrawImage(image, new Rectangle(0, 0, bitmap.Width, bitmap.Height), srcRect, GraphicsUnit.Pixel);
}
CroppedFrames[i] = bitmap;
}
GifEncoder(f.Width,f.Height);
return Image;
}
private void GifDecoder()
{
Stream bitmapStream = new FileStream(Path, FileMode.Open, FileAccess.Read, FileShare.Read);
GifBitmapDecoder gifBitmapDecoder = new GifBitmapDecoder(bitmapStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
Frames = new Image[gifBitmapDecoder.Frames.Count];
for (int i = 0; i < gifBitmapDecoder.Frames.Count; i++)
{
Frames[i] = BitmapFromSource(gifBitmapDecoder.Frames[i]);
}
}
private void GifEncoder(int width,int height)
{
BitmapPalette palette = new BitmapPalette(BitmapImageFromImage(Image),256);
int bytecount = (width * height) / 8;
using (FileStream fs = new FileStream(Path + "output.gif", FileMode.Create))
{
GifBitmapEncoder encoder = new GifBitmapEncoder();
for (int f = 0; f < Frames.Length; f++)
{
byte[] pixels = imageToByteArray(CroppedFrames[f]);
BitmapSource image = BitmapSource.Create(width,height,96,96,System.Windows.Media.PixelFormats.Indexed8,palette,pixels,width);
encoder.Frames.Add(BitmapFrame.Create(image));
}
encoder.Save(fs);
Image = Image.FromStream(fs);
fs.Close();
}
}
private byte[] imageToByteArray(Image imageIn)
{
MemoryStream ms = new MemoryStream();
Bitmap aaa = new Bitmap(imageIn);
aaa.Save(ms, ImageFormat.Png);
return ms.ToArray();
}
(bool isStrch, int slashwid, int slashhei) are for checking if the picturebox is smaller than the image or not to get accurate crops with the rect.

Converting a System.Windows.Media.Drawing object to Bitmap bytes

I have a System.Windows.Media.Drawing object that I am wanting to convert into a Bitmap object, and then from there extract the bytes that represent the image. I've looked about the internet and I can't seem to find how to do what I need, so any help would be appreciated.
So I finally found a way to convert a System.Windows.Media.Drawing object to a System.Drawing.Bitmap object, and from that get a byte[] object representing the image data. The following is not pretty but does actually work.
public static byte[] DrawingToBytes(Drawing drawing)
{
DrawingVisual visual = new DrawingVisual();
using (DrawingContext context = visual.RenderOpen())
{
// If using the BitmapEncoder uncomment the following line to get a white background.
// context.DrawRectangle(Brushes.White, null, drawing.bounds);
context.DrawDrawing(drawing);
}
int width = (int)(drawing.Bounds.Width)
int height = (int)(drawing.Bounds.Height)
Bitmap bmp = new Bitmap(width, height);
Bitmap bmpOut;
using (Graphics g = Graphics.FromImage(bmp))
{
g.Clear(System.Drawing.Color.White);
RenderTargetBitmap rtBmp = new RenderTargetBitmap(width, height,
bmp.HorizontalResolution,
bmp.VerticalResolution,
PixelFormats.Pbgra32);
rtBmp.Render(visual);
// Alternative using BmpBitmapEncoder, use in place of what comes after if you wish.
// MemoryStream stream = new MemoryStream();
// BitmapEncoder encoder = new BmpBitmapEncoder();
// encoder.Frames.Add(BitmapFrame.Create(rtBmp));
// encoder.save(stream);
int stride = width * ((rtBmp.Format.BitsPerPixel + 7) / 8);
byte[] bits = new byte[height * stride];
bitmapSource.CopyPixels(bits, stride, 0);
unsafe
{
fixed (byte* pBits = bits)
{
IntPtr ptr = new IntPtr(pBits);
bmpOut = new Bitmap(width, height, stride,
System.Drawing.Imaging.PixelFormat.Format32bppPArgb, ptr);
}
}
g.DrawImage(bmpOut, 0, 0, bmp.Width, bmp.Height);
}
byte[] bytes;
using (MemoryStream ms = new MemoryStream())
{
bmp.Save(ms, ImageFormat.bmp);
data = ms.ToArray();
}
return bytes;
}
So yeah, it's horrible but it actually works.
You can try that:
byte[] ImageToByte(Image image)
{
ImageConverter converter = new ImageConverter();
return (byte[])converter.ConvertTo(img, typeof(byte[]));
}
This work also for Bitmap.

Resizing gif file, save it to memory

I use the following code for resizing a gif file.
public void Resize(int newWidth, int newHeight)
{
var dimension = new FrameDimension(_image.FrameDimensionsList[0]);
var frameCount = _image.GetFrameCount(dimension);
// load frames
var encoder = new GifBitmapEncoder();
for (var i = 0; i < frameCount; ++i)
{
_image.SelectActiveFrame(dimension, i);
encoder.Frames.Add(BitmapFrame.Create(
new Bitmap(_image, newWidth, newHeight).CreateBitmapSource()));
}
using (var ms = new MemoryStream())
{
encoder.Save(ms);
ms.Position = 0;
_image = new Bitmap(ms);
}
}
public static BitmapSource CreateBitmapSource(this Bitmap bitmap)
{
if (bitmap == null)
throw new ArgumentNullException("bitmap");
lock (bitmap)
{
IntPtr hBitmap = bitmap.GetHbitmap();
return System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
hBitmap,
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
}
}
If I output it to a filestream, the gif is animated (altough the frames seem a little slower), but if i load it into an image, it doesn't matter wether from the memorystream or loading it from the "working" gif that came from the filestream, it only contains 1 frame.
What am I doing wrong?

Marshal.Copy/UnlockBits hangs

A user selects a portion of an image for a cut and paste operation. I create a new bitmap, paste the selected portion in the new image, wipe the source array and paste it back into the old image. Works but at least half the time it hangs with Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
Any thoughts or help?
public BitmapSource CutToNew(double left, double top, double width, double height, double pageWidth, double pageHeight)
{
var destBmp = new Bitmap((int)pageWidth, (int)pageHeight);
var g = Graphics.FromImage(destBmp);
g.FillRectangle(new SolidBrush(Color.White), 0, 0,
(int)pageHeight, (int)pageWidth);
g.Dispose();
var croppedArea = new Rectangle((int)left, (int)top, (int)width, (int)height);
BitmapData croppedSource = _bitmapImage.LockBits(croppedArea,
ImageLockMode.ReadWrite, BitmapImage.PixelFormat);
var croppedDestArea = new Rectangle((int)left, (int)top, (int)width, (int)height);
BitmapData croppedDest = destBmp.LockBits(croppedDestArea,
ImageLockMode.WriteOnly, BitmapImage.PixelFormat);
// Create data array to hold bmpSource pixel data
int stride = croppedSource.Stride;
int numBytes = stride * (int)height;
var srcData = new byte[numBytes];
var destData = new byte[numBytes];
Marshal.Copy(croppedSource.Scan0, srcData, 0, numBytes);
//Tried creating a separate array in case that helped.
Array.Copy(srcData, destData, srcData.Length);
//Often hangs here with Attempted to read or write protected memory.
Marshal.Copy(destData, 0, croppedDest.Scan0, numBytes);
destBmp.UnlockBits(croppedDest);
var retVal = new DocAppImage {BitmapImage = destBmp};
destBmp.Dispose();
//Blank the source area
for (int y = 0; y < srcData.Length; y++)
srcData[y] = 0xFF;
Marshal.Copy(srcData, 0, croppedSource.Scan0, numBytes);
_bitmapImage.UnlockBits(croppedSource);
return retVal.bmpSource;
}
private Bitmap _bitmapImage;
public Bitmap BitmapImage
{
get
{
if (_bitmapImage != null)
return _bitmapImage;
if (FileImage != null)
{
var stream = new MemoryStream(FileImage); //Fileimage=TIFF read from file.
_bitmapImage = new Bitmap(stream);
return _bitmapImage;
}
return null;
}
set
{
if (value != null)
{
ImageCodecInfo codecInfo = GetImageCodecInfo("TIFF");
... implementation to set the bitmap image.
You may want to try specifying your PixelFormat when you create the new object.
For example:
var destBmp = new Bitmap((int)pageWidth, (int)pageHeight, PixelFormat.Format24bppRgb);

Categories

Resources