WPF Bitmap / BitmapFrame to multi-page / multi-frame TIFF with JPEG encodingMVVM, WPF Ribbon V4, with...

Why are special aircraft used for the carriers in the United States Navy?

function only contains jump discontinuity but is not piecewise continuous

Why can't we use freedom of speech and expression to incite people to rebel against government?

How do you say “my friend is throwing a party, do you wanna come?” in german

Sometimes a banana is just a banana

What is a term for a function that when called repeatedly, has the same effect as calling once?

The reduction of NAD+

Should I use HTTPS on a domain that will only be used for redirection?

Called into a meeting and told we are being made redundant (laid off) and "not to share outside". Can I tell my partner?

Misplaced tyre lever - alternatives?

Can I become debt free or should I file for bankruptcy? How do I manage my debt and finances?

PTIJ: What dummy is the Gemara referring to?

How to set path in STY file as relative to this file path

Wardrobe above a wall with fuse boxes

How do I deal with being envious of my own players?

Script that counts quarters, dimes, nickels, and pennies

A bug in Excel? Conditional formatting for marking duplicates also highlights unique value

Is divide-by-zero a security vulnerability?

Correct physics behind the colors on CD (compact disc)?

Is there any relevance to Thor getting his hair cut other than comedic value?

Can I solder 12/2 Romex to extend wire 5 ft?

The need of reserving one's ability in job interviews

Specific Chinese carabiner QA?

What can I do if someone tampers with my SSH public key?



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













3












$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.










share|improve this question











$endgroup$

















    3












    $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.










    share|improve this question











    $endgroup$















      3












      3








      3





      $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.










      share|improve this question











      $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






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited yesterday









      Heslacher

      45.1k463159




      45.1k463159










      asked yesterday









      geometrikalgeometrikal

      235413




      235413






















          2 Answers
          2






          active

          oldest

          votes


















          5












          $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.






          share|improve this answer











          $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
            yesterday










          • $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
            yesterday





















          4












          $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.






          share|improve this answer









          $endgroup$













          • $begingroup$
            Some good points. Most of the WrapJpegs code comes from the linked SO question, so I'm still trying to work it out. I would be interested in what using a struct for the chunks of fields looks like
            $endgroup$
            – geometrikal
            11 hours ago











          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
          });


          }
          });














          draft saved

          draft discarded


















          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









          5












          $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.






          share|improve this answer











          $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
            yesterday










          • $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
            yesterday


















          5












          $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.






          share|improve this answer











          $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
            yesterday










          • $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
            yesterday
















          5












          5








          5





          $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.






          share|improve this answer











          $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.







          share|improve this answer














          share|improve this answer



          share|improve this answer








          edited 23 hours ago

























          answered yesterday









          Henrik HansenHenrik Hansen

          7,72011229




          7,72011229












          • $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
            yesterday










          • $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
            yesterday




















          • $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
            yesterday










          • $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
            yesterday


















          $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
          yesterday




          $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
          yesterday












          $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
          yesterday






          $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
          yesterday















          4












          $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.






          share|improve this answer









          $endgroup$













          • $begingroup$
            Some good points. Most of the WrapJpegs code comes from the linked SO question, so I'm still trying to work it out. I would be interested in what using a struct for the chunks of fields looks like
            $endgroup$
            – geometrikal
            11 hours ago
















          4












          $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.






          share|improve this answer









          $endgroup$













          • $begingroup$
            Some good points. Most of the WrapJpegs code comes from the linked SO question, so I'm still trying to work it out. I would be interested in what using a struct for the chunks of fields looks like
            $endgroup$
            – geometrikal
            11 hours ago














          4












          4








          4





          $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.






          share|improve this answer









          $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.







          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered 23 hours ago









          Peter TaylorPeter Taylor

          17.6k2962




          17.6k2962












          • $begingroup$
            Some good points. Most of the WrapJpegs code comes from the linked SO question, so I'm still trying to work it out. I would be interested in what using a struct for the chunks of fields looks like
            $endgroup$
            – geometrikal
            11 hours ago


















          • $begingroup$
            Some good points. Most of the WrapJpegs code comes from the linked SO question, so I'm still trying to work it out. I would be interested in what using a struct for the chunks of fields looks like
            $endgroup$
            – geometrikal
            11 hours ago
















          $begingroup$
          Some good points. Most of the WrapJpegs code comes from the linked SO question, so I'm still trying to work it out. I would be interested in what using a struct for the chunks of fields looks like
          $endgroup$
          – geometrikal
          11 hours ago




          $begingroup$
          Some good points. Most of the WrapJpegs code comes from the linked SO question, so I'm still trying to work it out. I would be interested in what using a struct for the chunks of fields looks like
          $endgroup$
          – geometrikal
          11 hours ago


















          draft saved

          draft discarded




















































          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.




          draft saved


          draft discarded














          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





















































          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







          Popular posts from this blog

          Why does my Macbook overheat and use so much CPU and energy when on YouTube?Why do so many insist on using...

          How to prevent page numbers from appearing on glossaries?How to remove a dot and a page number in the...

          Puerta de Hutt Referencias Enlaces externos Menú de navegación15°58′00″S 5°42′00″O /...