Пример #1
0
        /// <summary>
        /// Function to persist a <see cref="IGorgonImage"/> to a stream.
        /// </summary>
        /// <param name="imageData">A <see cref="IGorgonImage"/> to persist to the stream.</param>
        /// <param name="stream">The stream that will receive the image data.</param>
        /// <exception cref="ArgumentNullException">Thrown when the <paramref name="stream"/>, or the <paramref name="imageData"/> parameter is <b>null</b>.</exception>
        /// <exception cref="ArgumentEmptyException">Thrown when the <paramref name="stream"/> is read only.</exception>
        /// <exception cref="NotSupportedException">Thrown when the image data in the stream has a pixel format that is unsupported by the codec.</exception>
        /// <remarks>
        /// <para>
        /// When persisting image data via a codec, the image must have a format that the codec can recognize. This list of supported formats is provided by the <see cref="SupportedPixelFormats"/>
        /// property. Applications may convert their image data a supported format before saving the data using a codec.
        /// </para>
        /// </remarks>
        public override void SaveToStream(IGorgonImage imageData, Stream stream)
        {
            // Ensure that we can actually read this format.  We do not perform total pixel conversion on behalf of the user, they are responsible for that.
            // We will, however, support swizzling and pixel compression (e.g. 32 -> 24 bit).
            if (Array.IndexOf(_supportedFormats, imageData.Format) == -1)
            {
                throw new NotSupportedException(string.Format(Resources.GORIMG_ERR_FORMAT_NOT_SUPPORTED, imageData.Format));
            }

            using (var writer = new GorgonBinaryWriter(stream, true))
            {
                // Write the header for the file before we dump the file contents.
                TgaHeader header = GetHeader(imageData, out TGAConversionFlags conversionFlags);

                GorgonPitchLayout destPitch;

                if ((conversionFlags & TGAConversionFlags.RGB888) == TGAConversionFlags.RGB888)
                {
                    destPitch = new GorgonPitchLayout(imageData.Width * 3, imageData.Width * 3 * imageData.Height);
                }
                else
                {
                    var formatInfo = new GorgonFormatInfo(imageData.Format);
                    destPitch = formatInfo.GetPitchForFormat(imageData.Width, imageData.Height);
                }

                GorgonPitchLayout srcPitch = imageData.Buffers[0].PitchInformation;

                // If the two pitches are equal and we have no conversion requirements, then just write out the buffer.
                if ((destPitch == srcPitch) && (conversionFlags == TGAConversionFlags.None))
                {
                    writer.WriteValue(ref header);
                    writer.WriteRange(imageData.Buffers[0].Data, count: srcPitch.SlicePitch);
                    return;
                }

                unsafe
                {
                    // Get the pointer to the first mip/array/depth level.
                    byte *srcPointer = (byte *)imageData.Buffers[0].Data;
                    var   lineBuffer = new GorgonNativeBuffer <byte>(srcPitch.RowPitch);

                    try
                    {
                        // Persist the working buffer to the stream.
                        writer.WriteValue(ref header);

                        // Write out each scan line.
                        for (int y = 0; y < imageData.Height; y++)
                        {
                            byte *destPtr = (byte *)lineBuffer;

                            if ((conversionFlags & TGAConversionFlags.RGB888) == TGAConversionFlags.RGB888)
                            {
                                ImageUtilities.Compress24BPPScanLine(srcPointer,
                                                                     srcPitch.RowPitch,
                                                                     destPtr,
                                                                     destPitch.RowPitch,
                                                                     (conversionFlags & TGAConversionFlags.Swizzle) == TGAConversionFlags.Swizzle);
                            }
                            else if ((conversionFlags & TGAConversionFlags.Swizzle) == TGAConversionFlags.Swizzle)
                            {
                                ImageUtilities.SwizzleScanline(srcPointer, srcPitch.RowPitch, destPtr, destPitch.RowPitch, imageData.Format, ImageBitFlags.None);
                            }
                            else
                            {
                                ImageUtilities.CopyScanline(srcPointer, srcPitch.RowPitch, destPtr, destPitch.RowPitch, imageData.Format, ImageBitFlags.None);
                            }

                            srcPointer += srcPitch.RowPitch;

                            writer.WriteRange(lineBuffer, count: destPitch.RowPitch);
                        }
                    }
                    finally
                    {
                        lineBuffer?.Dispose();
                    }
                }
            }
        }
Пример #2
0
        /// <summary>
        /// Function to perform the copying of image data into the buffer.
        /// </summary>
        /// <param name="reader">A reader used to read the data from the source stream.</param>
        /// <param name="image">Image data.</param>
        /// <param name="conversionFlags">Flags used to convert the image.</param>
        private void CopyImageData(GorgonBinaryReader reader, IGorgonImage image, TGAConversionFlags conversionFlags)
        {
            // TGA only supports 1 array level, and 1 mip level, so we only need to get the first buffer.
            IGorgonImageBuffer buffer = image.Buffers[0];

            // Determine how large a row is, in bytes.
            var formatInfo = new GorgonFormatInfo(image.Format);

            GorgonPitchLayout srcPitch = (conversionFlags & TGAConversionFlags.Expand) == TGAConversionFlags.Expand
                                             ? new GorgonPitchLayout(image.Width * 3, image.Width * 3 * image.Height)
                                             : formatInfo.GetPitchForFormat(image.Width, image.Height);

            unsafe
            {
                // Otherwise, allocate a buffer for conversion.
                byte *destPtr = (byte *)buffer.Data;

                // Adjust destination for inverted axes.
                if ((conversionFlags & TGAConversionFlags.InvertX) == TGAConversionFlags.InvertX)
                {
                    destPtr += buffer.PitchInformation.RowPitch - formatInfo.SizeInBytes;
                }

                if ((conversionFlags & TGAConversionFlags.InvertY) != TGAConversionFlags.InvertY)
                {
                    destPtr += (image.Height - 1) * buffer.PitchInformation.RowPitch;
                }

                // Used to counter the number of lines to force as opaque.
                int opaqueLineCount = 0;
                // The buffer used to hold an uncompressed scanline.
                GorgonNativeBuffer <byte> lineBuffer = null;

                try
                {
                    for (int y = 0; y < image.Height; y++)
                    {
                        // Indicates that the scanline has an alpha of 0 for the entire run.
                        bool lineHasZeroAlpha;

                        if ((conversionFlags & TGAConversionFlags.RLE) == TGAConversionFlags.RLE)
                        {
                            lineHasZeroAlpha = ReadCompressed(reader, image.Width, destPtr, image.Format, conversionFlags);
                        }
                        else
                        {
                            // Read the current scanline into memory.
                            if (lineBuffer == null)
                            {
                                lineBuffer = new GorgonNativeBuffer <byte>(srcPitch.RowPitch);
                            }

                            reader.ReadRange(lineBuffer, count: srcPitch.RowPitch);

                            lineHasZeroAlpha = ReadUncompressed((byte *)lineBuffer, srcPitch.RowPitch, destPtr, image.Format, conversionFlags);
                        }

                        if ((lineHasZeroAlpha) && ((conversionFlags & TGAConversionFlags.SetOpaqueAlpha) == TGAConversionFlags.SetOpaqueAlpha))
                        {
                            opaqueLineCount++;
                        }

                        // The components of the pixel data in a TGA file need swizzling for 32 bit.
                        if (formatInfo.BitDepth == 32)
                        {
                            ImageUtilities.SwizzleScanline(destPtr,
                                                           buffer.PitchInformation.RowPitch,
                                                           destPtr,
                                                           buffer.PitchInformation.RowPitch,
                                                           image.Format,
                                                           ImageBitFlags.None);
                        }

                        if ((conversionFlags & TGAConversionFlags.InvertY) != TGAConversionFlags.InvertY)
                        {
                            destPtr -= buffer.PitchInformation.RowPitch;
                        }
                        else
                        {
                            destPtr += buffer.PitchInformation.RowPitch;
                        }
                    }
                }
                finally
                {
                    lineBuffer?.Dispose();
                }

                if (opaqueLineCount != image.Height)
                {
                    return;
                }

                // Set the alpha to opaque if we don't have any alpha values (i.e. alpha = 0 for all pixels).
                destPtr = (byte *)buffer.Data;
                for (int y = 0; y < image.Height; y++)
                {
                    ImageUtilities.CopyScanline(destPtr,
                                                buffer.PitchInformation.RowPitch,
                                                destPtr,
                                                buffer.PitchInformation.RowPitch,
                                                image.Format,
                                                ImageBitFlags.OpaqueAlpha);
                    destPtr += buffer.PitchInformation.RowPitch;
                }
            }
        }