WPF Bitmap / BitmapFrame to multi-page / multi-frame TIFF with JPEG encodingMVVM, WPF Ribbon V4, with...
Are small insurances worth it
Does "legal poaching" exist?
What are all the squawk codes?
How can I create a Table like this in Latex?
I can't die. Who am I?
Can throughput exceed the bandwidth of a network
Every subset equal to original set?
Why can't we make a perpetual motion machine by using a magnet to pull up a piece of metal, then letting it fall back down?
Would the melodic leap of the opening phrase of Mozart's K545 be considered dissonant?
How can atoms be electrically neutral when there is a difference in the positions of the charges?
It beats the alternative
Giving a talk in my old university, how prominently should I tell students my salary?
How can I be pwned if I'm not registered on the compromised site?
Create chunks from an array
What happened to QGIS 2.x LTR?
What could trigger powerful quakes on icy world?
Why won't the strings command stop?
Second-story floor leveling question for new home
WPF Bitmap / BitmapFrame to multi-page / multi-frame TIFF with JPEG encoding
How do you say “my friend is throwing a party, do you wanna come?” in german
It took me a lot of time to make this, pls like. (YouTube Comments #1)
Sometimes a banana is just a banana
What am I? I am in theaters and computer programs
How can I handle a player who pre-plans arguments about my rulings on RAW?
WPF Bitmap / BitmapFrame to multi-page / multi-frame TIFF with JPEG encoding
MVVM, WPF Ribbon V4, with PrismEncoding a screenshot to JPEG and saving it to a memorystreamWPF Window with different DataTemplatesParcelable Bitmap with the intention of uploading the bitmapLocating a bitmap inside another (larger) bitmap with F#Return IEnumerable<KeyValuePair> from a private method; use Dictionary or anon. type?Calculator with WPFWPF object model control with singleton and static messenger? ConditionalWeakTable?Updating WPF image from camera frame grabber threadWrite 16x16 bitmap to frame buffer
$begingroup$
I was searching for a long time how to write multi-page TIFFs with the JPEG encoding. TIFFs support JPEG encoded frames but the built-in encoder in .NET Framework does not have JPEG as a compression option.
The code is based on the answer to this question: https://stackoverflow.com/questions/14811496/tiff-with-jpeg-compression-much-larger-than-original-jpeg
but does not rely on the FreeImage library.
First up, a class to convert BitmapFrame
or Bitmap
to a JPEG image:
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Windows.Media.Imaging;
namespace TIFF
{
public class Jpeg
{
public byte[] Data;
public uint Width;
public uint Height;
public uint HorizontalResolution;
public uint VerticalResolution;
public Jpeg(byte[] data, uint width, uint height, uint horizontalResolution, uint verticalResolution)
{
this.Data = data;
this.Width = width;
this.Height = height;
this.HorizontalResolution = horizontalResolution;
this.VerticalResolution = verticalResolution;
}
public static Jpeg FromBitmapFrame(BitmapFrame bitmap, long quality)
{
Jpeg jpeg;
using (var stream = new MemoryStream())
{
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.QualityLevel = 90;
encoder.Frames.Add(bitmap);
encoder.Save(stream);
jpeg = new Jpeg(stream.ToArray(), (uint)bitmap.Width, (uint)bitmap.Height, (uint)bitmap.DpiX, (uint)bitmap.DpiY);
}
return jpeg;
}
public static Jpeg FromBitmap(Bitmap bitmap, long quality)
{
Jpeg jpeg;
using (var stream = new MemoryStream())
{
ImageCodecInfo jpgEncoder = GetEncoder(ImageFormat.Jpeg);
Encoder encoder = Encoder.Quality;
EncoderParameters parameters = new EncoderParameters(1);
parameters.Param[0] = new EncoderParameter(encoder, quality);
bitmap.Save(stream, jpgEncoder, parameters);
jpeg = new Jpeg(stream.ToArray(), (uint) bitmap.Width, (uint) bitmap.Height, (uint) bitmap.HorizontalResolution, (uint) bitmap.VerticalResolution);
}
return jpeg;
}
private static ImageCodecInfo GetEncoder(ImageFormat format)
{
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();
foreach (ImageCodecInfo codec in codecs)
{
if (codec.FormatID == format.Guid)
{
return codec;
}
}
return null;
}
}
}
Next, a class to create the TIFF image
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Windows.Media.Imaging;
namespace TIFF
{
public static class JpegTiff
{
public static byte[] Create(List<BitmapFrame> frames, long quality)
{
List<Jpeg> jpegs = new List<Jpeg>();
foreach (var frame in frames)
{
jpegs.Add(Jpeg.FromBitmapFrame(frame, quality));
}
return WrapJpegs(jpegs);
}
public static byte[] Create(List<Bitmap> bitmaps, string filename, long quality)
{
List<Jpeg> jpegs = new List<Jpeg>();
foreach (var bitmap in bitmaps)
{
jpegs.Add(Jpeg.FromBitmap(bitmap, quality));
}
return WrapJpegs(jpegs);
}
private static byte[] WrapJpegs(List<Jpeg> jpegs)
{
if (jpegs == null || jpegs.Count == 0 || jpegs.FindIndex(b => b.Data.Length == 0) > -1)
throw new ArgumentNullException("Image Data must not be null or empty");
MemoryStream tiffData = new MemoryStream();
BinaryWriter writer = new BinaryWriter(tiffData);
uint offset = 8; // size of header, offset to IFD
ushort entryCount = 14; // entries per IFD
#region IFH - Image file header
// magic number
if (BitConverter.IsLittleEndian)
writer.Write(0x002A4949);
else
writer.Write(0x4D4D002A);
// offset to (first) IFD
writer.Write(offset);
#endregion IFH
#region IFD Image file directory
// write image file directories for each jpeg
for (int i = 0; offset > 0; i++)
{
var jpeg = jpegs[i];
uint width = jpeg.Width;
uint length = jpeg.Height;
uint xres = jpeg.HorizontalResolution;
uint yres = jpeg.VerticalResolution;
// count of entries:
writer.Write(entryCount);
offset += 6 + 12 * (uint)entryCount; // add lengths of entries, entry-count and next-ifd-offset
// TIFF-fields / IFD-entrys:
// {TAG, TYPE (3 = short, 4 = long, 5 = rational), COUNT, VALUE/OFFSET}
uint[,] fields = new uint[,] {
{254, 4, 1, 0}, // NewSubfileType
{256, 4, 1, width}, // ImageWidth
{257, 4, 1, length}, // ImageLength
{258, 3, 3, offset}, // BitsPerSample
{259, 3, 1, 7}, // Compression (new JPEG)
{262, 3, 1, 6}, //PhotometricInterpretation (YCbCr)
{273, 4, 1, offset + 22}, // StripOffsets (offset IFH + entries + values of BitsPerSample & YResolution & XResolution)
{277, 3, 1, 3}, // SamplesPerPixel
{278, 4, 1, length}, // RowsPerStrip
{279, 4, 1, (uint)jpegs[i].Data.LongLength}, // StripByteCounts
{282, 5, 1, offset + 6}, // XResolution (offset IFH + entries + values of BitsPerSample)
{283, 5, 1, offset + 14}, // YResolution (offset IFH + entries + values of BitsPerSample & YResolution)
{284, 3, 1, 1}, // PlanarConfiguration (chunky)
{296, 3, 1, 2} // ResolutionUnit
};
// write fields
for (int f = 0; f < fields.GetLength(0); f++)
{
writer.Write((ushort)fields[f, 0]);
writer.Write((ushort)fields[f, 1]);
writer.Write(fields[f, 2]);
writer.Write(fields[f, 3]);
}
// offset of next IFD
if (i == jpegs.Count - 1)
offset = 0;
else
offset += 22 + (uint)jpegs[i].Data.LongLength; // add values (of fields) length and jpeg length
writer.Write(offset);
#region values of fields
// BitsPerSample
writer.Write((ushort)8);
writer.Write((ushort)8);
writer.Write((ushort)8);
// XResolution
writer.Write(xres);
writer.Write(1);
// YResolution
writer.Write(yres);
writer.Write(1);
#endregion values of fields
// actual image Data
writer.Write(jpegs[i].Data);
}
#endregion IFD
writer.Close();
return tiffData.ToArray();
}
}
}
It could probably be improved by passing in the stream to write to instead of returning a byte array.
c# image wpf compression
$endgroup$
add a comment |
$begingroup$
I was searching for a long time how to write multi-page TIFFs with the JPEG encoding. TIFFs support JPEG encoded frames but the built-in encoder in .NET Framework does not have JPEG as a compression option.
The code is based on the answer to this question: https://stackoverflow.com/questions/14811496/tiff-with-jpeg-compression-much-larger-than-original-jpeg
but does not rely on the FreeImage library.
First up, a class to convert BitmapFrame
or Bitmap
to a JPEG image:
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Windows.Media.Imaging;
namespace TIFF
{
public class Jpeg
{
public byte[] Data;
public uint Width;
public uint Height;
public uint HorizontalResolution;
public uint VerticalResolution;
public Jpeg(byte[] data, uint width, uint height, uint horizontalResolution, uint verticalResolution)
{
this.Data = data;
this.Width = width;
this.Height = height;
this.HorizontalResolution = horizontalResolution;
this.VerticalResolution = verticalResolution;
}
public static Jpeg FromBitmapFrame(BitmapFrame bitmap, long quality)
{
Jpeg jpeg;
using (var stream = new MemoryStream())
{
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.QualityLevel = 90;
encoder.Frames.Add(bitmap);
encoder.Save(stream);
jpeg = new Jpeg(stream.ToArray(), (uint)bitmap.Width, (uint)bitmap.Height, (uint)bitmap.DpiX, (uint)bitmap.DpiY);
}
return jpeg;
}
public static Jpeg FromBitmap(Bitmap bitmap, long quality)
{
Jpeg jpeg;
using (var stream = new MemoryStream())
{
ImageCodecInfo jpgEncoder = GetEncoder(ImageFormat.Jpeg);
Encoder encoder = Encoder.Quality;
EncoderParameters parameters = new EncoderParameters(1);
parameters.Param[0] = new EncoderParameter(encoder, quality);
bitmap.Save(stream, jpgEncoder, parameters);
jpeg = new Jpeg(stream.ToArray(), (uint) bitmap.Width, (uint) bitmap.Height, (uint) bitmap.HorizontalResolution, (uint) bitmap.VerticalResolution);
}
return jpeg;
}
private static ImageCodecInfo GetEncoder(ImageFormat format)
{
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();
foreach (ImageCodecInfo codec in codecs)
{
if (codec.FormatID == format.Guid)
{
return codec;
}
}
return null;
}
}
}
Next, a class to create the TIFF image
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Windows.Media.Imaging;
namespace TIFF
{
public static class JpegTiff
{
public static byte[] Create(List<BitmapFrame> frames, long quality)
{
List<Jpeg> jpegs = new List<Jpeg>();
foreach (var frame in frames)
{
jpegs.Add(Jpeg.FromBitmapFrame(frame, quality));
}
return WrapJpegs(jpegs);
}
public static byte[] Create(List<Bitmap> bitmaps, string filename, long quality)
{
List<Jpeg> jpegs = new List<Jpeg>();
foreach (var bitmap in bitmaps)
{
jpegs.Add(Jpeg.FromBitmap(bitmap, quality));
}
return WrapJpegs(jpegs);
}
private static byte[] WrapJpegs(List<Jpeg> jpegs)
{
if (jpegs == null || jpegs.Count == 0 || jpegs.FindIndex(b => b.Data.Length == 0) > -1)
throw new ArgumentNullException("Image Data must not be null or empty");
MemoryStream tiffData = new MemoryStream();
BinaryWriter writer = new BinaryWriter(tiffData);
uint offset = 8; // size of header, offset to IFD
ushort entryCount = 14; // entries per IFD
#region IFH - Image file header
// magic number
if (BitConverter.IsLittleEndian)
writer.Write(0x002A4949);
else
writer.Write(0x4D4D002A);
// offset to (first) IFD
writer.Write(offset);
#endregion IFH
#region IFD Image file directory
// write image file directories for each jpeg
for (int i = 0; offset > 0; i++)
{
var jpeg = jpegs[i];
uint width = jpeg.Width;
uint length = jpeg.Height;
uint xres = jpeg.HorizontalResolution;
uint yres = jpeg.VerticalResolution;
// count of entries:
writer.Write(entryCount);
offset += 6 + 12 * (uint)entryCount; // add lengths of entries, entry-count and next-ifd-offset
// TIFF-fields / IFD-entrys:
// {TAG, TYPE (3 = short, 4 = long, 5 = rational), COUNT, VALUE/OFFSET}
uint[,] fields = new uint[,] {
{254, 4, 1, 0}, // NewSubfileType
{256, 4, 1, width}, // ImageWidth
{257, 4, 1, length}, // ImageLength
{258, 3, 3, offset}, // BitsPerSample
{259, 3, 1, 7}, // Compression (new JPEG)
{262, 3, 1, 6}, //PhotometricInterpretation (YCbCr)
{273, 4, 1, offset + 22}, // StripOffsets (offset IFH + entries + values of BitsPerSample & YResolution & XResolution)
{277, 3, 1, 3}, // SamplesPerPixel
{278, 4, 1, length}, // RowsPerStrip
{279, 4, 1, (uint)jpegs[i].Data.LongLength}, // StripByteCounts
{282, 5, 1, offset + 6}, // XResolution (offset IFH + entries + values of BitsPerSample)
{283, 5, 1, offset + 14}, // YResolution (offset IFH + entries + values of BitsPerSample & YResolution)
{284, 3, 1, 1}, // PlanarConfiguration (chunky)
{296, 3, 1, 2} // ResolutionUnit
};
// write fields
for (int f = 0; f < fields.GetLength(0); f++)
{
writer.Write((ushort)fields[f, 0]);
writer.Write((ushort)fields[f, 1]);
writer.Write(fields[f, 2]);
writer.Write(fields[f, 3]);
}
// offset of next IFD
if (i == jpegs.Count - 1)
offset = 0;
else
offset += 22 + (uint)jpegs[i].Data.LongLength; // add values (of fields) length and jpeg length
writer.Write(offset);
#region values of fields
// BitsPerSample
writer.Write((ushort)8);
writer.Write((ushort)8);
writer.Write((ushort)8);
// XResolution
writer.Write(xres);
writer.Write(1);
// YResolution
writer.Write(yres);
writer.Write(1);
#endregion values of fields
// actual image Data
writer.Write(jpegs[i].Data);
}
#endregion IFD
writer.Close();
return tiffData.ToArray();
}
}
}
It could probably be improved by passing in the stream to write to instead of returning a byte array.
c# image wpf compression
$endgroup$
add a comment |
$begingroup$
I was searching for a long time how to write multi-page TIFFs with the JPEG encoding. TIFFs support JPEG encoded frames but the built-in encoder in .NET Framework does not have JPEG as a compression option.
The code is based on the answer to this question: https://stackoverflow.com/questions/14811496/tiff-with-jpeg-compression-much-larger-than-original-jpeg
but does not rely on the FreeImage library.
First up, a class to convert BitmapFrame
or Bitmap
to a JPEG image:
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Windows.Media.Imaging;
namespace TIFF
{
public class Jpeg
{
public byte[] Data;
public uint Width;
public uint Height;
public uint HorizontalResolution;
public uint VerticalResolution;
public Jpeg(byte[] data, uint width, uint height, uint horizontalResolution, uint verticalResolution)
{
this.Data = data;
this.Width = width;
this.Height = height;
this.HorizontalResolution = horizontalResolution;
this.VerticalResolution = verticalResolution;
}
public static Jpeg FromBitmapFrame(BitmapFrame bitmap, long quality)
{
Jpeg jpeg;
using (var stream = new MemoryStream())
{
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.QualityLevel = 90;
encoder.Frames.Add(bitmap);
encoder.Save(stream);
jpeg = new Jpeg(stream.ToArray(), (uint)bitmap.Width, (uint)bitmap.Height, (uint)bitmap.DpiX, (uint)bitmap.DpiY);
}
return jpeg;
}
public static Jpeg FromBitmap(Bitmap bitmap, long quality)
{
Jpeg jpeg;
using (var stream = new MemoryStream())
{
ImageCodecInfo jpgEncoder = GetEncoder(ImageFormat.Jpeg);
Encoder encoder = Encoder.Quality;
EncoderParameters parameters = new EncoderParameters(1);
parameters.Param[0] = new EncoderParameter(encoder, quality);
bitmap.Save(stream, jpgEncoder, parameters);
jpeg = new Jpeg(stream.ToArray(), (uint) bitmap.Width, (uint) bitmap.Height, (uint) bitmap.HorizontalResolution, (uint) bitmap.VerticalResolution);
}
return jpeg;
}
private static ImageCodecInfo GetEncoder(ImageFormat format)
{
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();
foreach (ImageCodecInfo codec in codecs)
{
if (codec.FormatID == format.Guid)
{
return codec;
}
}
return null;
}
}
}
Next, a class to create the TIFF image
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Windows.Media.Imaging;
namespace TIFF
{
public static class JpegTiff
{
public static byte[] Create(List<BitmapFrame> frames, long quality)
{
List<Jpeg> jpegs = new List<Jpeg>();
foreach (var frame in frames)
{
jpegs.Add(Jpeg.FromBitmapFrame(frame, quality));
}
return WrapJpegs(jpegs);
}
public static byte[] Create(List<Bitmap> bitmaps, string filename, long quality)
{
List<Jpeg> jpegs = new List<Jpeg>();
foreach (var bitmap in bitmaps)
{
jpegs.Add(Jpeg.FromBitmap(bitmap, quality));
}
return WrapJpegs(jpegs);
}
private static byte[] WrapJpegs(List<Jpeg> jpegs)
{
if (jpegs == null || jpegs.Count == 0 || jpegs.FindIndex(b => b.Data.Length == 0) > -1)
throw new ArgumentNullException("Image Data must not be null or empty");
MemoryStream tiffData = new MemoryStream();
BinaryWriter writer = new BinaryWriter(tiffData);
uint offset = 8; // size of header, offset to IFD
ushort entryCount = 14; // entries per IFD
#region IFH - Image file header
// magic number
if (BitConverter.IsLittleEndian)
writer.Write(0x002A4949);
else
writer.Write(0x4D4D002A);
// offset to (first) IFD
writer.Write(offset);
#endregion IFH
#region IFD Image file directory
// write image file directories for each jpeg
for (int i = 0; offset > 0; i++)
{
var jpeg = jpegs[i];
uint width = jpeg.Width;
uint length = jpeg.Height;
uint xres = jpeg.HorizontalResolution;
uint yres = jpeg.VerticalResolution;
// count of entries:
writer.Write(entryCount);
offset += 6 + 12 * (uint)entryCount; // add lengths of entries, entry-count and next-ifd-offset
// TIFF-fields / IFD-entrys:
// {TAG, TYPE (3 = short, 4 = long, 5 = rational), COUNT, VALUE/OFFSET}
uint[,] fields = new uint[,] {
{254, 4, 1, 0}, // NewSubfileType
{256, 4, 1, width}, // ImageWidth
{257, 4, 1, length}, // ImageLength
{258, 3, 3, offset}, // BitsPerSample
{259, 3, 1, 7}, // Compression (new JPEG)
{262, 3, 1, 6}, //PhotometricInterpretation (YCbCr)
{273, 4, 1, offset + 22}, // StripOffsets (offset IFH + entries + values of BitsPerSample & YResolution & XResolution)
{277, 3, 1, 3}, // SamplesPerPixel
{278, 4, 1, length}, // RowsPerStrip
{279, 4, 1, (uint)jpegs[i].Data.LongLength}, // StripByteCounts
{282, 5, 1, offset + 6}, // XResolution (offset IFH + entries + values of BitsPerSample)
{283, 5, 1, offset + 14}, // YResolution (offset IFH + entries + values of BitsPerSample & YResolution)
{284, 3, 1, 1}, // PlanarConfiguration (chunky)
{296, 3, 1, 2} // ResolutionUnit
};
// write fields
for (int f = 0; f < fields.GetLength(0); f++)
{
writer.Write((ushort)fields[f, 0]);
writer.Write((ushort)fields[f, 1]);
writer.Write(fields[f, 2]);
writer.Write(fields[f, 3]);
}
// offset of next IFD
if (i == jpegs.Count - 1)
offset = 0;
else
offset += 22 + (uint)jpegs[i].Data.LongLength; // add values (of fields) length and jpeg length
writer.Write(offset);
#region values of fields
// BitsPerSample
writer.Write((ushort)8);
writer.Write((ushort)8);
writer.Write((ushort)8);
// XResolution
writer.Write(xres);
writer.Write(1);
// YResolution
writer.Write(yres);
writer.Write(1);
#endregion values of fields
// actual image Data
writer.Write(jpegs[i].Data);
}
#endregion IFD
writer.Close();
return tiffData.ToArray();
}
}
}
It could probably be improved by passing in the stream to write to instead of returning a byte array.
c# image wpf compression
$endgroup$
I was searching for a long time how to write multi-page TIFFs with the JPEG encoding. TIFFs support JPEG encoded frames but the built-in encoder in .NET Framework does not have JPEG as a compression option.
The code is based on the answer to this question: https://stackoverflow.com/questions/14811496/tiff-with-jpeg-compression-much-larger-than-original-jpeg
but does not rely on the FreeImage library.
First up, a class to convert BitmapFrame
or Bitmap
to a JPEG image:
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Windows.Media.Imaging;
namespace TIFF
{
public class Jpeg
{
public byte[] Data;
public uint Width;
public uint Height;
public uint HorizontalResolution;
public uint VerticalResolution;
public Jpeg(byte[] data, uint width, uint height, uint horizontalResolution, uint verticalResolution)
{
this.Data = data;
this.Width = width;
this.Height = height;
this.HorizontalResolution = horizontalResolution;
this.VerticalResolution = verticalResolution;
}
public static Jpeg FromBitmapFrame(BitmapFrame bitmap, long quality)
{
Jpeg jpeg;
using (var stream = new MemoryStream())
{
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.QualityLevel = 90;
encoder.Frames.Add(bitmap);
encoder.Save(stream);
jpeg = new Jpeg(stream.ToArray(), (uint)bitmap.Width, (uint)bitmap.Height, (uint)bitmap.DpiX, (uint)bitmap.DpiY);
}
return jpeg;
}
public static Jpeg FromBitmap(Bitmap bitmap, long quality)
{
Jpeg jpeg;
using (var stream = new MemoryStream())
{
ImageCodecInfo jpgEncoder = GetEncoder(ImageFormat.Jpeg);
Encoder encoder = Encoder.Quality;
EncoderParameters parameters = new EncoderParameters(1);
parameters.Param[0] = new EncoderParameter(encoder, quality);
bitmap.Save(stream, jpgEncoder, parameters);
jpeg = new Jpeg(stream.ToArray(), (uint) bitmap.Width, (uint) bitmap.Height, (uint) bitmap.HorizontalResolution, (uint) bitmap.VerticalResolution);
}
return jpeg;
}
private static ImageCodecInfo GetEncoder(ImageFormat format)
{
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();
foreach (ImageCodecInfo codec in codecs)
{
if (codec.FormatID == format.Guid)
{
return codec;
}
}
return null;
}
}
}
Next, a class to create the TIFF image
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Windows.Media.Imaging;
namespace TIFF
{
public static class JpegTiff
{
public static byte[] Create(List<BitmapFrame> frames, long quality)
{
List<Jpeg> jpegs = new List<Jpeg>();
foreach (var frame in frames)
{
jpegs.Add(Jpeg.FromBitmapFrame(frame, quality));
}
return WrapJpegs(jpegs);
}
public static byte[] Create(List<Bitmap> bitmaps, string filename, long quality)
{
List<Jpeg> jpegs = new List<Jpeg>();
foreach (var bitmap in bitmaps)
{
jpegs.Add(Jpeg.FromBitmap(bitmap, quality));
}
return WrapJpegs(jpegs);
}
private static byte[] WrapJpegs(List<Jpeg> jpegs)
{
if (jpegs == null || jpegs.Count == 0 || jpegs.FindIndex(b => b.Data.Length == 0) > -1)
throw new ArgumentNullException("Image Data must not be null or empty");
MemoryStream tiffData = new MemoryStream();
BinaryWriter writer = new BinaryWriter(tiffData);
uint offset = 8; // size of header, offset to IFD
ushort entryCount = 14; // entries per IFD
#region IFH - Image file header
// magic number
if (BitConverter.IsLittleEndian)
writer.Write(0x002A4949);
else
writer.Write(0x4D4D002A);
// offset to (first) IFD
writer.Write(offset);
#endregion IFH
#region IFD Image file directory
// write image file directories for each jpeg
for (int i = 0; offset > 0; i++)
{
var jpeg = jpegs[i];
uint width = jpeg.Width;
uint length = jpeg.Height;
uint xres = jpeg.HorizontalResolution;
uint yres = jpeg.VerticalResolution;
// count of entries:
writer.Write(entryCount);
offset += 6 + 12 * (uint)entryCount; // add lengths of entries, entry-count and next-ifd-offset
// TIFF-fields / IFD-entrys:
// {TAG, TYPE (3 = short, 4 = long, 5 = rational), COUNT, VALUE/OFFSET}
uint[,] fields = new uint[,] {
{254, 4, 1, 0}, // NewSubfileType
{256, 4, 1, width}, // ImageWidth
{257, 4, 1, length}, // ImageLength
{258, 3, 3, offset}, // BitsPerSample
{259, 3, 1, 7}, // Compression (new JPEG)
{262, 3, 1, 6}, //PhotometricInterpretation (YCbCr)
{273, 4, 1, offset + 22}, // StripOffsets (offset IFH + entries + values of BitsPerSample & YResolution & XResolution)
{277, 3, 1, 3}, // SamplesPerPixel
{278, 4, 1, length}, // RowsPerStrip
{279, 4, 1, (uint)jpegs[i].Data.LongLength}, // StripByteCounts
{282, 5, 1, offset + 6}, // XResolution (offset IFH + entries + values of BitsPerSample)
{283, 5, 1, offset + 14}, // YResolution (offset IFH + entries + values of BitsPerSample & YResolution)
{284, 3, 1, 1}, // PlanarConfiguration (chunky)
{296, 3, 1, 2} // ResolutionUnit
};
// write fields
for (int f = 0; f < fields.GetLength(0); f++)
{
writer.Write((ushort)fields[f, 0]);
writer.Write((ushort)fields[f, 1]);
writer.Write(fields[f, 2]);
writer.Write(fields[f, 3]);
}
// offset of next IFD
if (i == jpegs.Count - 1)
offset = 0;
else
offset += 22 + (uint)jpegs[i].Data.LongLength; // add values (of fields) length and jpeg length
writer.Write(offset);
#region values of fields
// BitsPerSample
writer.Write((ushort)8);
writer.Write((ushort)8);
writer.Write((ushort)8);
// XResolution
writer.Write(xres);
writer.Write(1);
// YResolution
writer.Write(yres);
writer.Write(1);
#endregion values of fields
// actual image Data
writer.Write(jpegs[i].Data);
}
#endregion IFD
writer.Close();
return tiffData.ToArray();
}
}
}
It could probably be improved by passing in the stream to write to instead of returning a byte array.
c# image wpf compression
c# image wpf compression
edited 7 hours ago
Heslacher
45.1k463159
45.1k463159
asked 8 hours ago
geometrikalgeometrikal
235413
235413
add a comment |
add a comment |
2 Answers
2
active
oldest
votes
$begingroup$
public static Jpeg FromBitmapFrame(BitmapFrame bitmap, long quality)
{
Jpeg jpeg;
using (var stream = new MemoryStream())
{
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.QualityLevel = 90;
encoder.Frames.Add(bitmap);
encoder.Save(stream);
jpeg = new Jpeg(stream.ToArray(), (uint)bitmap.Width, (uint)bitmap.Height, (uint)bitmap.DpiX, (uint)bitmap.DpiY);
}
return jpeg;
}
Here it should be safe to return from inside the using
statement:
public static Jpeg FromBitmapFrame(BitmapFrame bitmap, long quality)
{
using (var stream = new MemoryStream())
{
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.QualityLevel = 90;
encoder.Frames.Add(bitmap);
encoder.Save(stream);
return new Jpeg(stream.ToArray(), (uint)bitmap.Width, (uint)bitmap.Height, (uint)bitmap.DpiX, (uint)bitmap.DpiY);
}
}
private static ImageCodecInfo GetEncoder(ImageFormat format)
{
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();
foreach (ImageCodecInfo codec in codecs)
{
if (codec.FormatID == format.Guid)
{
return codec;
}
}
return null;
}
Using LINQ this can be reduced to a oneliner:
private static ImageCodecInfo GetEncoder(ImageFormat format)
{
return ImageCodecInfo.GetImageDecoders().FirstOrDefault(codec => codec.FormatID == format.Guid);
}
public static byte[] Create(List<BitmapFrame> frames, long quality)
{
List<Jpeg> jpegs = new List<Jpeg>();
foreach (var frame in frames)
{
jpegs.Add(Jpeg.FromBitmapFrame(frame, quality));
}
return WrapJpegs(jpegs);
}
Again LINQ can "modernize" this a little:
public static byte[] Create(IEnumerable<BitmapFrame> frames, long quality)
{
return WrapJpegs(frames.Select(frame => Jpeg.FromBitmapFrame(frame, quality)).ToList());
}
jpegs.FindIndex(b => b.Data.Length == 0) > -1
LINQ:
jpegs.Any(j => j.Data.Length == 0)
IMO easier to read.
MemoryStream tiffData = new MemoryStream();
BinaryWriter writer = new BinaryWriter(tiffData);
You need to wrap these in using
statements:
using (MemoryStream tiffData = new MemoryStream()) // HDH Use using in order to clean up
using (BinaryWriter writer = new BinaryWriter(tiffData))
{
...
writer.Flush();
return tiffData.ToArray();
}
for (int i = 0; offset > 0; i++)
{
The stop condition is confusing. Why not just use jpegs.Count
because you actually iterate through all items anyway.
In the main loop you declare this:
var jpeg = jpegs[i];
But you use jpegs[i]
several times in the loop. Be consistent.
uint[,] fields = new uint[,] {
{254, 4, 1, 0}, // NewSubfileType
{256, 4, 1, width}, // ImageWidth
I think I would make a struct or class for these fields in order to be strict with types - hence avoiding the casting.
writer.Write((ushort)8);
writer.Write((ushort)8);
writer.Write((ushort)8);
make a const ushort bitsPerSample = 8;
for this - before the loop.
The idea of having a stream
as argument to the methods is good, but be aware that BinaryWriter
disposes the stream, when it is disposed unless you use the constructor with the leaveOpen
flag.
$endgroup$
$begingroup$
Nice stuff with the LINQ, looks great. BTW is it better to edit the original question with these changes or post another answer?
$endgroup$
– geometrikal
1 hour ago
$begingroup$
@geometrikal: You're not allowed to change the question once an answer has been posted. Feel free to make an answer with the changes or post a new question with an updated version :-)
$endgroup$
– Henrik Hansen
1 hour ago
add a comment |
$begingroup$
Henrik covered a number of points which I would have raised, so I won't repeat those.
namespace TIFF
{
public class Jpeg
seems inconsistent to me. The convention in .Net is to camel-case acronyms and initialisms, so the class name is as expected and the namespace is not.
public static class JpegTiff
{
public static byte[] Create(List<BitmapFrame> frames, long quality)
...
public static byte[] Create(List<Bitmap> bitmaps, string filename, long quality)
...
private static byte[] WrapJpegs(List<Jpeg> jpegs)
...
Since Jpeg
is a public class it seems to me that you could rename WrapJpegs
to Create
and make it public. That opens up the option, for example, of encoding different frames at different qualities. (I'd also change List
to IEnumerable
, as Henrik proposes for the existing Create
methods).
There are some worrying magic numbers. Some of these concerns might be alleviated by a comment with a URL for the file format specification.
uint offset = 8; // size of header, offset to IFD
Is this 4 for the endianness magic number and 4 for offset
itself?
ushort entryCount = 14; // entries per IFD
This is fields.GetLength(0)
, isn't it? Is there any reason that you can't explicitly use fields.GetLength(0)
for robustness if you later add or remove a field?
// TIFF-fields / IFD-entrys:
// {TAG, TYPE (3 = short, 4 = long, 5 = rational), COUNT, VALUE/OFFSET}
uint[,] fields = ...
// write fields
for (int f = 0; f < fields.GetLength(0); f++)
{
writer.Write((ushort)fields[f, 0]);
writer.Write((ushort)fields[f, 1]);
writer.Write(fields[f, 2]);
writer.Write(fields[f, 3]);
}
I find it very confusing that a short
and a long
should take the same amount of space.
if (i == jpegs.Count - 1)
offset = 0;
else
offset += 22 + (uint)jpegs[i].Data.LongLength; // add values (of fields) length and jpeg length
22? Three shorts and four ints?
One more explicit (although less portable, I admit) approach to these lengths would be to use structs for the chunks of fields and Marshal.SizeOf
. But I understand if you think that's overkill.
$endgroup$
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f214812%2fwpf-bitmap-bitmapframe-to-multi-page-multi-frame-tiff-with-jpeg-encoding%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
$begingroup$
public static Jpeg FromBitmapFrame(BitmapFrame bitmap, long quality)
{
Jpeg jpeg;
using (var stream = new MemoryStream())
{
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.QualityLevel = 90;
encoder.Frames.Add(bitmap);
encoder.Save(stream);
jpeg = new Jpeg(stream.ToArray(), (uint)bitmap.Width, (uint)bitmap.Height, (uint)bitmap.DpiX, (uint)bitmap.DpiY);
}
return jpeg;
}
Here it should be safe to return from inside the using
statement:
public static Jpeg FromBitmapFrame(BitmapFrame bitmap, long quality)
{
using (var stream = new MemoryStream())
{
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.QualityLevel = 90;
encoder.Frames.Add(bitmap);
encoder.Save(stream);
return new Jpeg(stream.ToArray(), (uint)bitmap.Width, (uint)bitmap.Height, (uint)bitmap.DpiX, (uint)bitmap.DpiY);
}
}
private static ImageCodecInfo GetEncoder(ImageFormat format)
{
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();
foreach (ImageCodecInfo codec in codecs)
{
if (codec.FormatID == format.Guid)
{
return codec;
}
}
return null;
}
Using LINQ this can be reduced to a oneliner:
private static ImageCodecInfo GetEncoder(ImageFormat format)
{
return ImageCodecInfo.GetImageDecoders().FirstOrDefault(codec => codec.FormatID == format.Guid);
}
public static byte[] Create(List<BitmapFrame> frames, long quality)
{
List<Jpeg> jpegs = new List<Jpeg>();
foreach (var frame in frames)
{
jpegs.Add(Jpeg.FromBitmapFrame(frame, quality));
}
return WrapJpegs(jpegs);
}
Again LINQ can "modernize" this a little:
public static byte[] Create(IEnumerable<BitmapFrame> frames, long quality)
{
return WrapJpegs(frames.Select(frame => Jpeg.FromBitmapFrame(frame, quality)).ToList());
}
jpegs.FindIndex(b => b.Data.Length == 0) > -1
LINQ:
jpegs.Any(j => j.Data.Length == 0)
IMO easier to read.
MemoryStream tiffData = new MemoryStream();
BinaryWriter writer = new BinaryWriter(tiffData);
You need to wrap these in using
statements:
using (MemoryStream tiffData = new MemoryStream()) // HDH Use using in order to clean up
using (BinaryWriter writer = new BinaryWriter(tiffData))
{
...
writer.Flush();
return tiffData.ToArray();
}
for (int i = 0; offset > 0; i++)
{
The stop condition is confusing. Why not just use jpegs.Count
because you actually iterate through all items anyway.
In the main loop you declare this:
var jpeg = jpegs[i];
But you use jpegs[i]
several times in the loop. Be consistent.
uint[,] fields = new uint[,] {
{254, 4, 1, 0}, // NewSubfileType
{256, 4, 1, width}, // ImageWidth
I think I would make a struct or class for these fields in order to be strict with types - hence avoiding the casting.
writer.Write((ushort)8);
writer.Write((ushort)8);
writer.Write((ushort)8);
make a const ushort bitsPerSample = 8;
for this - before the loop.
The idea of having a stream
as argument to the methods is good, but be aware that BinaryWriter
disposes the stream, when it is disposed unless you use the constructor with the leaveOpen
flag.
$endgroup$
$begingroup$
Nice stuff with the LINQ, looks great. BTW is it better to edit the original question with these changes or post another answer?
$endgroup$
– geometrikal
1 hour ago
$begingroup$
@geometrikal: You're not allowed to change the question once an answer has been posted. Feel free to make an answer with the changes or post a new question with an updated version :-)
$endgroup$
– Henrik Hansen
1 hour ago
add a comment |
$begingroup$
public static Jpeg FromBitmapFrame(BitmapFrame bitmap, long quality)
{
Jpeg jpeg;
using (var stream = new MemoryStream())
{
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.QualityLevel = 90;
encoder.Frames.Add(bitmap);
encoder.Save(stream);
jpeg = new Jpeg(stream.ToArray(), (uint)bitmap.Width, (uint)bitmap.Height, (uint)bitmap.DpiX, (uint)bitmap.DpiY);
}
return jpeg;
}
Here it should be safe to return from inside the using
statement:
public static Jpeg FromBitmapFrame(BitmapFrame bitmap, long quality)
{
using (var stream = new MemoryStream())
{
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.QualityLevel = 90;
encoder.Frames.Add(bitmap);
encoder.Save(stream);
return new Jpeg(stream.ToArray(), (uint)bitmap.Width, (uint)bitmap.Height, (uint)bitmap.DpiX, (uint)bitmap.DpiY);
}
}
private static ImageCodecInfo GetEncoder(ImageFormat format)
{
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();
foreach (ImageCodecInfo codec in codecs)
{
if (codec.FormatID == format.Guid)
{
return codec;
}
}
return null;
}
Using LINQ this can be reduced to a oneliner:
private static ImageCodecInfo GetEncoder(ImageFormat format)
{
return ImageCodecInfo.GetImageDecoders().FirstOrDefault(codec => codec.FormatID == format.Guid);
}
public static byte[] Create(List<BitmapFrame> frames, long quality)
{
List<Jpeg> jpegs = new List<Jpeg>();
foreach (var frame in frames)
{
jpegs.Add(Jpeg.FromBitmapFrame(frame, quality));
}
return WrapJpegs(jpegs);
}
Again LINQ can "modernize" this a little:
public static byte[] Create(IEnumerable<BitmapFrame> frames, long quality)
{
return WrapJpegs(frames.Select(frame => Jpeg.FromBitmapFrame(frame, quality)).ToList());
}
jpegs.FindIndex(b => b.Data.Length == 0) > -1
LINQ:
jpegs.Any(j => j.Data.Length == 0)
IMO easier to read.
MemoryStream tiffData = new MemoryStream();
BinaryWriter writer = new BinaryWriter(tiffData);
You need to wrap these in using
statements:
using (MemoryStream tiffData = new MemoryStream()) // HDH Use using in order to clean up
using (BinaryWriter writer = new BinaryWriter(tiffData))
{
...
writer.Flush();
return tiffData.ToArray();
}
for (int i = 0; offset > 0; i++)
{
The stop condition is confusing. Why not just use jpegs.Count
because you actually iterate through all items anyway.
In the main loop you declare this:
var jpeg = jpegs[i];
But you use jpegs[i]
several times in the loop. Be consistent.
uint[,] fields = new uint[,] {
{254, 4, 1, 0}, // NewSubfileType
{256, 4, 1, width}, // ImageWidth
I think I would make a struct or class for these fields in order to be strict with types - hence avoiding the casting.
writer.Write((ushort)8);
writer.Write((ushort)8);
writer.Write((ushort)8);
make a const ushort bitsPerSample = 8;
for this - before the loop.
The idea of having a stream
as argument to the methods is good, but be aware that BinaryWriter
disposes the stream, when it is disposed unless you use the constructor with the leaveOpen
flag.
$endgroup$
$begingroup$
Nice stuff with the LINQ, looks great. BTW is it better to edit the original question with these changes or post another answer?
$endgroup$
– geometrikal
1 hour ago
$begingroup$
@geometrikal: You're not allowed to change the question once an answer has been posted. Feel free to make an answer with the changes or post a new question with an updated version :-)
$endgroup$
– Henrik Hansen
1 hour ago
add a comment |
$begingroup$
public static Jpeg FromBitmapFrame(BitmapFrame bitmap, long quality)
{
Jpeg jpeg;
using (var stream = new MemoryStream())
{
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.QualityLevel = 90;
encoder.Frames.Add(bitmap);
encoder.Save(stream);
jpeg = new Jpeg(stream.ToArray(), (uint)bitmap.Width, (uint)bitmap.Height, (uint)bitmap.DpiX, (uint)bitmap.DpiY);
}
return jpeg;
}
Here it should be safe to return from inside the using
statement:
public static Jpeg FromBitmapFrame(BitmapFrame bitmap, long quality)
{
using (var stream = new MemoryStream())
{
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.QualityLevel = 90;
encoder.Frames.Add(bitmap);
encoder.Save(stream);
return new Jpeg(stream.ToArray(), (uint)bitmap.Width, (uint)bitmap.Height, (uint)bitmap.DpiX, (uint)bitmap.DpiY);
}
}
private static ImageCodecInfo GetEncoder(ImageFormat format)
{
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();
foreach (ImageCodecInfo codec in codecs)
{
if (codec.FormatID == format.Guid)
{
return codec;
}
}
return null;
}
Using LINQ this can be reduced to a oneliner:
private static ImageCodecInfo GetEncoder(ImageFormat format)
{
return ImageCodecInfo.GetImageDecoders().FirstOrDefault(codec => codec.FormatID == format.Guid);
}
public static byte[] Create(List<BitmapFrame> frames, long quality)
{
List<Jpeg> jpegs = new List<Jpeg>();
foreach (var frame in frames)
{
jpegs.Add(Jpeg.FromBitmapFrame(frame, quality));
}
return WrapJpegs(jpegs);
}
Again LINQ can "modernize" this a little:
public static byte[] Create(IEnumerable<BitmapFrame> frames, long quality)
{
return WrapJpegs(frames.Select(frame => Jpeg.FromBitmapFrame(frame, quality)).ToList());
}
jpegs.FindIndex(b => b.Data.Length == 0) > -1
LINQ:
jpegs.Any(j => j.Data.Length == 0)
IMO easier to read.
MemoryStream tiffData = new MemoryStream();
BinaryWriter writer = new BinaryWriter(tiffData);
You need to wrap these in using
statements:
using (MemoryStream tiffData = new MemoryStream()) // HDH Use using in order to clean up
using (BinaryWriter writer = new BinaryWriter(tiffData))
{
...
writer.Flush();
return tiffData.ToArray();
}
for (int i = 0; offset > 0; i++)
{
The stop condition is confusing. Why not just use jpegs.Count
because you actually iterate through all items anyway.
In the main loop you declare this:
var jpeg = jpegs[i];
But you use jpegs[i]
several times in the loop. Be consistent.
uint[,] fields = new uint[,] {
{254, 4, 1, 0}, // NewSubfileType
{256, 4, 1, width}, // ImageWidth
I think I would make a struct or class for these fields in order to be strict with types - hence avoiding the casting.
writer.Write((ushort)8);
writer.Write((ushort)8);
writer.Write((ushort)8);
make a const ushort bitsPerSample = 8;
for this - before the loop.
The idea of having a stream
as argument to the methods is good, but be aware that BinaryWriter
disposes the stream, when it is disposed unless you use the constructor with the leaveOpen
flag.
$endgroup$
public static Jpeg FromBitmapFrame(BitmapFrame bitmap, long quality)
{
Jpeg jpeg;
using (var stream = new MemoryStream())
{
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.QualityLevel = 90;
encoder.Frames.Add(bitmap);
encoder.Save(stream);
jpeg = new Jpeg(stream.ToArray(), (uint)bitmap.Width, (uint)bitmap.Height, (uint)bitmap.DpiX, (uint)bitmap.DpiY);
}
return jpeg;
}
Here it should be safe to return from inside the using
statement:
public static Jpeg FromBitmapFrame(BitmapFrame bitmap, long quality)
{
using (var stream = new MemoryStream())
{
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.QualityLevel = 90;
encoder.Frames.Add(bitmap);
encoder.Save(stream);
return new Jpeg(stream.ToArray(), (uint)bitmap.Width, (uint)bitmap.Height, (uint)bitmap.DpiX, (uint)bitmap.DpiY);
}
}
private static ImageCodecInfo GetEncoder(ImageFormat format)
{
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();
foreach (ImageCodecInfo codec in codecs)
{
if (codec.FormatID == format.Guid)
{
return codec;
}
}
return null;
}
Using LINQ this can be reduced to a oneliner:
private static ImageCodecInfo GetEncoder(ImageFormat format)
{
return ImageCodecInfo.GetImageDecoders().FirstOrDefault(codec => codec.FormatID == format.Guid);
}
public static byte[] Create(List<BitmapFrame> frames, long quality)
{
List<Jpeg> jpegs = new List<Jpeg>();
foreach (var frame in frames)
{
jpegs.Add(Jpeg.FromBitmapFrame(frame, quality));
}
return WrapJpegs(jpegs);
}
Again LINQ can "modernize" this a little:
public static byte[] Create(IEnumerable<BitmapFrame> frames, long quality)
{
return WrapJpegs(frames.Select(frame => Jpeg.FromBitmapFrame(frame, quality)).ToList());
}
jpegs.FindIndex(b => b.Data.Length == 0) > -1
LINQ:
jpegs.Any(j => j.Data.Length == 0)
IMO easier to read.
MemoryStream tiffData = new MemoryStream();
BinaryWriter writer = new BinaryWriter(tiffData);
You need to wrap these in using
statements:
using (MemoryStream tiffData = new MemoryStream()) // HDH Use using in order to clean up
using (BinaryWriter writer = new BinaryWriter(tiffData))
{
...
writer.Flush();
return tiffData.ToArray();
}
for (int i = 0; offset > 0; i++)
{
The stop condition is confusing. Why not just use jpegs.Count
because you actually iterate through all items anyway.
In the main loop you declare this:
var jpeg = jpegs[i];
But you use jpegs[i]
several times in the loop. Be consistent.
uint[,] fields = new uint[,] {
{254, 4, 1, 0}, // NewSubfileType
{256, 4, 1, width}, // ImageWidth
I think I would make a struct or class for these fields in order to be strict with types - hence avoiding the casting.
writer.Write((ushort)8);
writer.Write((ushort)8);
writer.Write((ushort)8);
make a const ushort bitsPerSample = 8;
for this - before the loop.
The idea of having a stream
as argument to the methods is good, but be aware that BinaryWriter
disposes the stream, when it is disposed unless you use the constructor with the leaveOpen
flag.
edited 1 hour ago
answered 1 hour ago
Henrik HansenHenrik Hansen
7,70011229
7,70011229
$begingroup$
Nice stuff with the LINQ, looks great. BTW is it better to edit the original question with these changes or post another answer?
$endgroup$
– geometrikal
1 hour ago
$begingroup$
@geometrikal: You're not allowed to change the question once an answer has been posted. Feel free to make an answer with the changes or post a new question with an updated version :-)
$endgroup$
– Henrik Hansen
1 hour ago
add a comment |
$begingroup$
Nice stuff with the LINQ, looks great. BTW is it better to edit the original question with these changes or post another answer?
$endgroup$
– geometrikal
1 hour ago
$begingroup$
@geometrikal: You're not allowed to change the question once an answer has been posted. Feel free to make an answer with the changes or post a new question with an updated version :-)
$endgroup$
– Henrik Hansen
1 hour ago
$begingroup$
Nice stuff with the LINQ, looks great. BTW is it better to edit the original question with these changes or post another answer?
$endgroup$
– geometrikal
1 hour ago
$begingroup$
Nice stuff with the LINQ, looks great. BTW is it better to edit the original question with these changes or post another answer?
$endgroup$
– geometrikal
1 hour ago
$begingroup$
@geometrikal: You're not allowed to change the question once an answer has been posted. Feel free to make an answer with the changes or post a new question with an updated version :-)
$endgroup$
– Henrik Hansen
1 hour ago
$begingroup$
@geometrikal: You're not allowed to change the question once an answer has been posted. Feel free to make an answer with the changes or post a new question with an updated version :-)
$endgroup$
– Henrik Hansen
1 hour ago
add a comment |
$begingroup$
Henrik covered a number of points which I would have raised, so I won't repeat those.
namespace TIFF
{
public class Jpeg
seems inconsistent to me. The convention in .Net is to camel-case acronyms and initialisms, so the class name is as expected and the namespace is not.
public static class JpegTiff
{
public static byte[] Create(List<BitmapFrame> frames, long quality)
...
public static byte[] Create(List<Bitmap> bitmaps, string filename, long quality)
...
private static byte[] WrapJpegs(List<Jpeg> jpegs)
...
Since Jpeg
is a public class it seems to me that you could rename WrapJpegs
to Create
and make it public. That opens up the option, for example, of encoding different frames at different qualities. (I'd also change List
to IEnumerable
, as Henrik proposes for the existing Create
methods).
There are some worrying magic numbers. Some of these concerns might be alleviated by a comment with a URL for the file format specification.
uint offset = 8; // size of header, offset to IFD
Is this 4 for the endianness magic number and 4 for offset
itself?
ushort entryCount = 14; // entries per IFD
This is fields.GetLength(0)
, isn't it? Is there any reason that you can't explicitly use fields.GetLength(0)
for robustness if you later add or remove a field?
// TIFF-fields / IFD-entrys:
// {TAG, TYPE (3 = short, 4 = long, 5 = rational), COUNT, VALUE/OFFSET}
uint[,] fields = ...
// write fields
for (int f = 0; f < fields.GetLength(0); f++)
{
writer.Write((ushort)fields[f, 0]);
writer.Write((ushort)fields[f, 1]);
writer.Write(fields[f, 2]);
writer.Write(fields[f, 3]);
}
I find it very confusing that a short
and a long
should take the same amount of space.
if (i == jpegs.Count - 1)
offset = 0;
else
offset += 22 + (uint)jpegs[i].Data.LongLength; // add values (of fields) length and jpeg length
22? Three shorts and four ints?
One more explicit (although less portable, I admit) approach to these lengths would be to use structs for the chunks of fields and Marshal.SizeOf
. But I understand if you think that's overkill.
$endgroup$
add a comment |
$begingroup$
Henrik covered a number of points which I would have raised, so I won't repeat those.
namespace TIFF
{
public class Jpeg
seems inconsistent to me. The convention in .Net is to camel-case acronyms and initialisms, so the class name is as expected and the namespace is not.
public static class JpegTiff
{
public static byte[] Create(List<BitmapFrame> frames, long quality)
...
public static byte[] Create(List<Bitmap> bitmaps, string filename, long quality)
...
private static byte[] WrapJpegs(List<Jpeg> jpegs)
...
Since Jpeg
is a public class it seems to me that you could rename WrapJpegs
to Create
and make it public. That opens up the option, for example, of encoding different frames at different qualities. (I'd also change List
to IEnumerable
, as Henrik proposes for the existing Create
methods).
There are some worrying magic numbers. Some of these concerns might be alleviated by a comment with a URL for the file format specification.
uint offset = 8; // size of header, offset to IFD
Is this 4 for the endianness magic number and 4 for offset
itself?
ushort entryCount = 14; // entries per IFD
This is fields.GetLength(0)
, isn't it? Is there any reason that you can't explicitly use fields.GetLength(0)
for robustness if you later add or remove a field?
// TIFF-fields / IFD-entrys:
// {TAG, TYPE (3 = short, 4 = long, 5 = rational), COUNT, VALUE/OFFSET}
uint[,] fields = ...
// write fields
for (int f = 0; f < fields.GetLength(0); f++)
{
writer.Write((ushort)fields[f, 0]);
writer.Write((ushort)fields[f, 1]);
writer.Write(fields[f, 2]);
writer.Write(fields[f, 3]);
}
I find it very confusing that a short
and a long
should take the same amount of space.
if (i == jpegs.Count - 1)
offset = 0;
else
offset += 22 + (uint)jpegs[i].Data.LongLength; // add values (of fields) length and jpeg length
22? Three shorts and four ints?
One more explicit (although less portable, I admit) approach to these lengths would be to use structs for the chunks of fields and Marshal.SizeOf
. But I understand if you think that's overkill.
$endgroup$
add a comment |
$begingroup$
Henrik covered a number of points which I would have raised, so I won't repeat those.
namespace TIFF
{
public class Jpeg
seems inconsistent to me. The convention in .Net is to camel-case acronyms and initialisms, so the class name is as expected and the namespace is not.
public static class JpegTiff
{
public static byte[] Create(List<BitmapFrame> frames, long quality)
...
public static byte[] Create(List<Bitmap> bitmaps, string filename, long quality)
...
private static byte[] WrapJpegs(List<Jpeg> jpegs)
...
Since Jpeg
is a public class it seems to me that you could rename WrapJpegs
to Create
and make it public. That opens up the option, for example, of encoding different frames at different qualities. (I'd also change List
to IEnumerable
, as Henrik proposes for the existing Create
methods).
There are some worrying magic numbers. Some of these concerns might be alleviated by a comment with a URL for the file format specification.
uint offset = 8; // size of header, offset to IFD
Is this 4 for the endianness magic number and 4 for offset
itself?
ushort entryCount = 14; // entries per IFD
This is fields.GetLength(0)
, isn't it? Is there any reason that you can't explicitly use fields.GetLength(0)
for robustness if you later add or remove a field?
// TIFF-fields / IFD-entrys:
// {TAG, TYPE (3 = short, 4 = long, 5 = rational), COUNT, VALUE/OFFSET}
uint[,] fields = ...
// write fields
for (int f = 0; f < fields.GetLength(0); f++)
{
writer.Write((ushort)fields[f, 0]);
writer.Write((ushort)fields[f, 1]);
writer.Write(fields[f, 2]);
writer.Write(fields[f, 3]);
}
I find it very confusing that a short
and a long
should take the same amount of space.
if (i == jpegs.Count - 1)
offset = 0;
else
offset += 22 + (uint)jpegs[i].Data.LongLength; // add values (of fields) length and jpeg length
22? Three shorts and four ints?
One more explicit (although less portable, I admit) approach to these lengths would be to use structs for the chunks of fields and Marshal.SizeOf
. But I understand if you think that's overkill.
$endgroup$
Henrik covered a number of points which I would have raised, so I won't repeat those.
namespace TIFF
{
public class Jpeg
seems inconsistent to me. The convention in .Net is to camel-case acronyms and initialisms, so the class name is as expected and the namespace is not.
public static class JpegTiff
{
public static byte[] Create(List<BitmapFrame> frames, long quality)
...
public static byte[] Create(List<Bitmap> bitmaps, string filename, long quality)
...
private static byte[] WrapJpegs(List<Jpeg> jpegs)
...
Since Jpeg
is a public class it seems to me that you could rename WrapJpegs
to Create
and make it public. That opens up the option, for example, of encoding different frames at different qualities. (I'd also change List
to IEnumerable
, as Henrik proposes for the existing Create
methods).
There are some worrying magic numbers. Some of these concerns might be alleviated by a comment with a URL for the file format specification.
uint offset = 8; // size of header, offset to IFD
Is this 4 for the endianness magic number and 4 for offset
itself?
ushort entryCount = 14; // entries per IFD
This is fields.GetLength(0)
, isn't it? Is there any reason that you can't explicitly use fields.GetLength(0)
for robustness if you later add or remove a field?
// TIFF-fields / IFD-entrys:
// {TAG, TYPE (3 = short, 4 = long, 5 = rational), COUNT, VALUE/OFFSET}
uint[,] fields = ...
// write fields
for (int f = 0; f < fields.GetLength(0); f++)
{
writer.Write((ushort)fields[f, 0]);
writer.Write((ushort)fields[f, 1]);
writer.Write(fields[f, 2]);
writer.Write(fields[f, 3]);
}
I find it very confusing that a short
and a long
should take the same amount of space.
if (i == jpegs.Count - 1)
offset = 0;
else
offset += 22 + (uint)jpegs[i].Data.LongLength; // add values (of fields) length and jpeg length
22? Three shorts and four ints?
One more explicit (although less portable, I admit) approach to these lengths would be to use structs for the chunks of fields and Marshal.SizeOf
. But I understand if you think that's overkill.
answered 26 mins ago
Peter TaylorPeter Taylor
17.6k2862
17.6k2862
add a comment |
add a comment |
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f214812%2fwpf-bitmap-bitmapframe-to-multi-page-multi-frame-tiff-with-jpeg-encoding%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown