/// <summary> /// Losslessly transform the JPEG image associated with this transformer instance into one or more JPEG images /// stored in the given destination buffers. Lossless transforms work by moving the raw coefficients from one /// JPEG image structure to another without altering the values of the coefficients. While this is typically /// faster than decompressing the image, transforming it, and re-compressing it, lossless transforms are not /// free. Each lossless transform requires reading and performing Huffman decoding on all of the coefficients /// in the source image, regardless of the size of the destination image, thus this method provides a means of /// generating multiple transformed images from the same source or of applying multiple transformations /// simultaneously, in order to eliminate the need to read the source coefficients multiple times. /// </summary> /// <param name="destBuffers"> /// An array of image buffers. <paramref name="destBuffers" />[i] will receive a JPEG image that has been /// transformed using the parameters in <paramref name="transforms" />[i]. Use /// <see cref="NativeMethods.bufSize" /> to determine the maximum size for each buffer based on the /// transformed or cropped width and height and the chroma subsampling used in the source image. /// </param> /// <param name="transforms">An array of <seealso cref="TurboJpegTransform" /> instances, each of which /// specifies the transform parameters and/or cropping region for the corresponding transformed output image. /// </param> /// <param name="options">The flags to use for the transforms.</param> public void Transform(byte[][] destBuffers, TurboJpegTransform[] transforms, TurboJpegFlags options) { Contract.Requires(destBuffers != null); Contract.Requires(transforms != null); Contract.Requires(transforms.Length == destBuffers.Length); Contract.Requires(Enum.IsDefined(typeof(TransformOptions), options)); Contract.Ensures(Contract.ForAll(destBuffers, _ => _ != null)); Contract.Assume(this.JpegBuffer != null, "JPEG buffer not initialized"); var count = destBuffers.Length; var destBufferPtrs = new IntPtr[count]; this.transformedSizes = new int[count]; if (NativeMethods.transform(this.Handle, this.JpegBuffer, this.JpegSize, count, ref destBufferPtrs, ref this.transformedSizes, transforms, options) != 0) { throw new Exception(TurboJpegInterop.GetLastError()); } // we now have the result in buffers on the unmanaged heap for (var i = 0; i < count; ++i) { destBuffers[i] = new byte[this.transformedSizes[i]]; Marshal.Copy(destBufferPtrs[i], destBuffers[i], 0, this.transformedSizes[i]); } }
/// <summary> /// Decompress the JPEG source image associated with this decompressor instance and output a YUV planar image to the given /// destination buffer. This method performs JPEG decompression but leaves out the colour conversion step, so a planar YUV /// image is generated instead of an RGB image. The padding of the planes in this image is the same as in the images /// generated by <seealso cref="TurboJpegCompressor#EncodeYuv(byte[], int)" />. /// <para> /// NOTE: Technically, the JPEG format uses the YCbCr colourspace, but per the convention of the digital video /// community, the TurboJPEG API uses "YUV" to refer to an image format consisting of Y, Cb, and Cr image planes. /// </para> /// </summary> /// <param name="flags">The <see cref="TurboJpegFlags" /> controlling decompression.</param> public TurboJpegBuffer DecompressToYuv(TurboJpegFlags flags) { Contract.Requires(Enum.IsDefined(typeof(TurboJpegFlags), flags)); Contract.Ensures(Contract.Result <TurboJpegBuffer>() .BufferSize > 0, "output buffer must have non-zero size"); Contract.Assume(this.jpegBuffer != null, "No JPEG image is associated with this instance"); var bufferSize = NativeMethods.bufSizeYUV(this.jpegWidth, 4, this.jpegHeight, this.jpegSubsampling); using (var buffer = new TurboJpegSafeHandle(NativeMethods.alloc(bufferSize))) { var ptr = buffer.DangerousGetHandle(); if (NativeMethods.decompressToYUV(this.Handle, this.jpegBuffer, this.jpegBuffer.Length, ref ptr, this.jpegWidth, 4, this.jpegHeight, flags) != 0) { throw new Exception(TurboJpegInterop.GetLastError()); } // we now have the result in a buffer on the unmanaged heap. return(new TurboJpegBuffer(ptr, bufferSize)); } }
/// <summary> /// Decompress the JPEG source image associated with this decompressor instance and output a decompressed image to /// the given destination buffer. /// </summary> /// <param name="desiredWidth">The desired width (in pixels) of the decompressed image. If the desired image /// dimensions are different than the dimensions of the JPEG image being decompressed, then TurboJPEG will use /// scaling in the JPEG decompressor to generate the largest possible image that will fit within the desired /// dimensions. Setting this to 0 is the same as setting it to the width of the JPEG image; in other words the /// width will not be considered when determining the scaled image size). /// </param> /// <param name="pitch">The number of bytes per line of the destination image. Normally, this should be set to /// <code>scaledWidth * <see cref="TurboJpegUtilities.GetPixelSize" />(pixelFormat)</code> if the decompressed /// image is unpadded, but you can use this to, for instance, pad each line of the decompressed image to a /// 4-byte boundary or to decompress the JPEG image into a region of a larger image. NOTE: <code>scaledWidth</code> /// can be determined by calling /// <code>scalingFactor.<seealso cref="TurboJpegScalingFactor#getScaled getScaled" />(jpegWidth)</code> or by /// calling <seealso cref="GetScaledWidth" />. Setting this parameter to 0 is the equivalent of setting it to /// <code>scaledWidth * <see cref="TurboJpegUtilities.GetPixelSize" />(pixelFormat)</code>.</param> /// <param name="desiredHeight"> /// The desired Height (in pixels) of the decompressed image (or image region.) If the desired /// image dimensions are different than the dimensions of the JPEG image being decompressed, then TurboJPEG will use /// scaling in the JPEG decompressor to generate the largest possible image that will fit within the desired dimensions. /// Setting this to 0 is the same as setting it to the Height of the JPEG image (in other words, the Height will not be /// considered when determining the scaled image size.) /// </param> /// <param name="pixelFormat"> /// pixel format of the decompressed/decoded image (one of /// <seealso cref="TurboJpegUtilities#Rgb TurboJpegUtilities.PF_*" />) /// </param> /// <param name="flags"> /// the bitwise OR of one or more of /// <seealso cref="TurboJpegUtilities#BottomUp TurboJpegUtilities.FLAG_*" /> /// </param> public TurboJpegBuffer Decompress(int desiredWidth, int pitch, int desiredHeight, PixelFormat pixelFormat, TurboJpegFlags flags) { Contract.Requires(desiredWidth >= 0, "desiredWidth must be non-negative"); Contract.Requires(desiredHeight >= 0, "desiredHeight must be non-negative"); Contract.Requires(pitch >= 0, "pitch must be non-negative"); Contract.Requires(Enum.IsDefined(typeof(TurboJpegFlags), flags)); Contract.Ensures(Contract.Result <TurboJpegBuffer>() .BufferSize > 0, "output buffer must have non-zero size"); Contract.Assume(this.jpegBuffer != null, "No JPEG image is associated with this instance"); var pixelSize = TurboJpegUtilities.GetPixelSize(pixelFormat); var scaledWidth = this.GetScaledWidth(desiredWidth, desiredHeight); var scaledHeight = this.GetScaledHeight(desiredWidth, desiredHeight); if (pitch == 0) { pitch = scaledWidth * pixelSize; } var bufferSize = pitch * scaledHeight; // having allocated memory with the libjpeg-turbo allocator, we must ensure that we release it with the // matching deallocator lest Bad Things happen. Unlike compress, where the initial buffer size is a best // guess, we know the dimensions of the uncompressed image and the number of bits per pixel and so sizing // it appropriately is trivial and the buffer will never be reallocated using (var buffer = new TurboJpegSafeHandle(NativeMethods.alloc(bufferSize))) { var ptr = buffer.DangerousGetHandle(); if (NativeMethods.decompress(this.Handle, this.jpegBuffer, this.jpegBuffer.Length, ptr, desiredWidth, pitch, desiredHeight, pixelFormat, flags) != 0) { throw new Exception(TurboJpegInterop.GetLastError()); } // we now have the result in a buffer on the unmanaged heap. return(new TurboJpegBuffer(ptr, bufferSize)); } }
/// <summary> /// Losslessly transform the JPEG image associated with this transformer instance and return an array of /// <seealso cref="TurboJpegDecompressor" /> instances, each of which has a transformed JPEG image associated /// with it. /// </summary> /// <param name="transforms">An array of <see cref="TurboJpegTransform" /> instances, each of which specifies /// the transform parameters and/or cropping region for the corresponding transformed output image.</param> /// <param name="flags">The flags to use when transforming.</param> /// <param name="preallocateBuffers">If set to <c>true</c>, preallocate buffers.</param> /// <returns> /// An array of <seealso cref="TurboJpegDecompressor" /> instances, each of which has a transformed /// JPEG image associated with it. /// </returns> public TurboJpegDecompressor[] Transform(TurboJpegTransform[] transforms, TurboJpegFlags flags, bool preallocateBuffers) { Contract.Requires(transforms != null); Contract.Requires(Enum.IsDefined(typeof(TransformOptions), flags)); Contract.Ensures(Contract.Result <TurboJpegDecompressor[]>() != null); Contract.Ensures(Contract.Result <TurboJpegDecompressor[]>().Length == transforms.Length); Contract.Ensures(Contract.ForAll(Contract.Result <TurboJpegDecompressor[]>(), _ => _ != null)); var count = transforms.Length; var destinationBuffers = new byte[count][]; Contract.Assume(this.Width > 0, "JPEG buffer not initialized"); Contract.Assume(this.Height > 0, "JPEG buffer not initialized"); var decompressors = new TurboJpegDecompressor[count]; if (preallocateBuffers) { for (var i = 0; i < count; i++) { var width = this.Width; var height = this.Height; if ((transforms[i].Options & TransformOptions.Crop) != 0) { if (transforms[i].Width != 0) { width = transforms[i].Width; } if (transforms[i].Height != 0) { height = transforms[i].Height; } } destinationBuffers[i] = new byte[NativeMethods.bufSize(width, height, this.Subsampling)]; } this.Transform(destinationBuffers, transforms, flags); for (var i = 0; i < count; i++) { decompressors[i] = new TurboJpegDecompressor(destinationBuffers[i]); } } else { var destBufferPtrs = new IntPtr[count]; this.transformedSizes = new int[count]; if (NativeMethods.transform(this.Handle, this.JpegBuffer, this.JpegSize, count, ref destBufferPtrs, ref this.transformedSizes, transforms, flags) != 0) { throw new Exception(TurboJpegInterop.GetLastError()); } // we now have the result in buffers on the unmanaged heap for (var i = 0; i < count; ++i) { decompressors[i] = new TurboJpegDecompressor(); decompressors[i].SetJpegImage(destBufferPtrs[i]); } } return(decompressors); }