123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726 |
- using System;
- using System.ComponentModel;
- using System.Drawing;
- using System.IO;
- using System.Runtime.InteropServices;
- namespace PaintDotNet.Data
- {
- public sealed class TgaFileType : InternalFileType
- {
- public enum PropertyNames
- {
- BitDepth = 0,
- RleCompress = 1
- }
- public enum TgaBitDepthUIChoices
- {
- AutoDetect = 0,
- Bpp32 = 1,
- Bpp24 = 2
- }
- private enum TgaType
- : byte
- {
- Null = 0,
- Map = 1,
- Rgb = 2,
- Mono = 3,
- RleMap = 9,
- RleRgb = 10,
- RleMono = 11,
- CompMap = 32,
- CompMap4 = 33
- }
- [StructLayout(LayoutKind.Sequential)]
- private struct TgaHeader
- {
- public byte idLength; // Image ID Field Length
- public byte cmapType; // Color Map Type
- public TgaType imageType; // Image Type
- public ushort cmapIndex; // First Entry Index
- public ushort cmapLength; // Color Map Length
- public byte cmapEntrySize; // Color Map Entry Size
- public ushort xOrigin; // X-origin of Image
- public ushort yOrigin; // Y-origin of Image
- public ushort imageWidth; // Image Width
- public ushort imageHeight; // Image Height
- public byte pixelDepth; // Pixel Depth
- public byte imageDesc; // Image Descriptor
- public void Write(Stream output)
- {
- output.WriteByte(this.idLength);
- output.WriteByte(this.cmapType);
- output.WriteByte((byte)this.imageType);
- Utility.WriteUInt16(output, this.cmapIndex);
- Utility.WriteUInt16(output, this.cmapLength);
- output.WriteByte(this.cmapEntrySize);
- Utility.WriteUInt16(output, this.xOrigin);
- Utility.WriteUInt16(output, this.yOrigin);
- Utility.WriteUInt16(output, this.imageWidth);
- Utility.WriteUInt16(output, this.imageHeight);
- output.WriteByte(this.pixelDepth);
- output.WriteByte(this.imageDesc);
- }
- public TgaHeader(Stream input)
- {
- int byteRead = input.ReadByte();
- if (byteRead == -1)
- {
- throw new EndOfStreamException();
- }
- else
- {
- this.idLength = (byte)byteRead;
- }
- byteRead = input.ReadByte();
- if (byteRead == -1)
- {
- throw new EndOfStreamException();
- }
- else
- {
- this.cmapType = (byte)byteRead;
- }
- byteRead = input.ReadByte();
- if (byteRead == -1)
- {
- throw new EndOfStreamException();
- }
- else
- {
- this.imageType = (TgaType)byteRead;
- }
- int shortRead = Utility.ReadUInt16(input);
- if (shortRead == -1)
- {
- throw new EndOfStreamException();
- }
- else
- {
- this.cmapIndex = (ushort)shortRead;
- }
- shortRead = Utility.ReadUInt16(input);
- if (shortRead == -1)
- {
- throw new EndOfStreamException();
- }
- else
- {
- this.cmapLength = (ushort)shortRead;
- }
- byteRead = input.ReadByte();
- if (byteRead == -1)
- {
- throw new EndOfStreamException();
- }
- else
- {
- this.cmapEntrySize = (byte)byteRead;
- }
- shortRead = Utility.ReadUInt16(input);
- if (shortRead == -1)
- {
- throw new EndOfStreamException();
- }
- else
- {
- this.xOrigin = (ushort)shortRead;
- }
- shortRead = Utility.ReadUInt16(input);
- if (shortRead == -1)
- {
- throw new EndOfStreamException();
- }
- else
- {
- this.yOrigin = (ushort)shortRead;
- }
- shortRead = Utility.ReadUInt16(input);
- if (shortRead == -1)
- {
- throw new EndOfStreamException();
- }
- else
- {
- this.imageWidth = (ushort)shortRead;
- }
- shortRead = Utility.ReadUInt16(input);
- if (shortRead == -1)
- {
- throw new EndOfStreamException();
- }
- else
- {
- this.imageHeight = (ushort)shortRead;
- }
- byteRead = input.ReadByte();
- if (byteRead == -1)
- {
- throw new EndOfStreamException();
- }
- else
- {
- this.pixelDepth = (byte)byteRead;
- }
- byteRead = input.ReadByte();
- if (byteRead == -1)
- {
- throw new EndOfStreamException();
- }
- else
- {
- this.imageDesc = (byte)byteRead;
- }
- }
- }
- public override Document OnLoad(System.IO.Stream input)
- {
- TgaHeader header = new TgaHeader(input);
- bool compressed;
- switch (header.imageType)
- {
- case TgaType.Map:
- case TgaType.Rgb:
- case TgaType.Mono:
- compressed = false;
- break;
- case TgaType.RleMap:
- case TgaType.RleRgb:
- case TgaType.RleMono:
- compressed = true;
- break;
- default:
- throw new FormatException("unknown TGA image type");
- }
- if (header.imageWidth == 0 ||
- header.imageHeight == 0 ||
- header.pixelDepth == 0 ||
- header.cmapLength > 256)
- {
- throw new FormatException("bad TGA header");
- }
- if (header.pixelDepth != 8 &&
- header.pixelDepth != 15 &&
- header.pixelDepth != 16 &&
- header.pixelDepth != 24 &&
- header.pixelDepth != 32)
- {
- throw new FormatException("bad TGA header: pixelDepth not one of {8, 15, 16, 24, 32}");
- }
- if (header.idLength > 0)
- {
- input.Position += header.idLength; // skip descriptor
- }
- //BitmapLayer layer = Layer.CreateBackgroundLayer(header.imageWidth, header.imageHeight);
- try
- {
- Surface surface = new Surface(new Size(header.imageWidth, header.imageHeight));//layer.Surface;
- surface.Clear((ColorBgra)0xffffffff);
- ColorBgra[] palette = null;
- if (header.cmapType != 0)
- {
- palette = LoadPalette(input, header.cmapLength);
- }
- if (header.imageType == TgaType.Mono ||
- header.imageType == TgaType.RleMono)
- {
- palette = CreateGrayPalette();
- }
- // Bits 4 & 5 of the image descriptor byte control the ordering of the pixels
- bool xReversed = ((header.imageDesc & 16) == 16);
- bool yReversed = ((header.imageDesc & 32) == 32);
- byte rleLeftOver = 255; // for images with illegal packet boundary
- for (int y = 0; y < header.imageHeight; ++y)
- {
- MemoryBlock dstRow;
- if (yReversed)
- {
- dstRow = surface.GetRow(y);
- }
- else
- {
- dstRow = surface.GetRow(header.imageHeight - y - 1);
- }
- if (compressed)
- {
- rleLeftOver = ExpandCompressedLine(dstRow, 0, ref header, input, header.imageWidth, y, rleLeftOver, palette);
- }
- else
- {
- ExpandUncompressedLine(dstRow, 0, ref header, input, header.imageWidth, y, 0, palette);
- }
- }
- if (xReversed)
- {
- MirrorX(surface);
- }
- Document document = new Document(surface.Width, surface.Height);
- //document.Layers.Add(layer);
- return document;
- }
- catch
- {
- /*if (layer != null)
- {
- layer.Dispose();
- layer = null;
- }*/
- throw;
- }
- }
- private void MirrorX(Surface surface)
- {
- for (int y = 0; y < surface.Height; ++y)
- {
- for (int x = 0; x < surface.Width / 2; ++x)
- {
- ColorBgra rightSide = surface[surface.Width - x - 1, y];
- surface[surface.Width - x - 1, y] = surface[x, y];
- surface[x, y] = rightSide;
- }
- }
- }
- private ColorBgra[] CreateGrayPalette()
- {
- ColorBgra[] palette = new ColorBgra[256];
- for (int i = 0; i < palette.Length; ++i)
- {
- palette[i] = ColorBgra.FromBgra((byte)i, (byte)i, (byte)i, 255);
- }
- return palette;
- }
- private ColorBgra[] LoadPalette(Stream input, int count)
- {
- ColorBgra[] palette = new ColorBgra[count];
- for (int i = 0; i < palette.Length; ++i)
- {
- int blue = input.ReadByte();
- if (blue == -1)
- {
- throw new EndOfStreamException();
- }
- int green = input.ReadByte();
- if (green == -1)
- {
- throw new EndOfStreamException();
- }
- int red = input.ReadByte();
- if (red == -1)
- {
- throw new EndOfStreamException();
- }
- palette[i] = ColorBgra.FromBgra((byte)blue, (byte)green, (byte)red, 255);
- }
- return palette;
- }
- private byte ExpandCompressedLine(MemoryBlock dst, int dstIndex, ref TgaHeader header, Stream input, int width, int y, byte rleLeftOver, ColorBgra[] palette)
- {
- byte rle;
- long filePos = 0;
- int x = 0;
- while (x < width)
- {
- if (rleLeftOver != 255)
- {
- rle = rleLeftOver;
- rleLeftOver = 255;
- }
- else
- {
- int byte1 = input.ReadByte();
- if (byte1 == -1)
- {
- throw new EndOfStreamException();
- }
- else
- {
- rle = (byte)byte1;
- }
- }
- if ((rle & 128) != 0)
- {
- // RLE Encoded packet
- rle -= 127; // calculate real repeat count
- if ((x + rle) > width)
- {
- rleLeftOver = (byte)(128 + (rle - (width - x) - 1));
- filePos = input.Position;
- rle = (byte)(width - x);
- }
- ColorBgra color = ReadColor(input, header.pixelDepth, palette);
- for (int ix = 0; ix < rle; ++ix)
- {
- int index = dstIndex + (ix * ColorBgra.SizeOf);
- dst[index] = color[0];
- dst[1 + index] = color[1];
- dst[2 + index] = color[2];
- dst[3 + index] = color[3];
- }
- if (rleLeftOver != 255)
- {
- input.Position = filePos;
- }
- }
- else
- {
- // Raw packet
- rle += 1; // calculate real repeat count
- if ((x + rle) > width)
- {
- rleLeftOver = (byte)(rle - (width - x) - 1);
- rle = (byte)(width - x);
- }
- ExpandUncompressedLine(dst, dstIndex, ref header, input, rle, y, x, palette);
- }
- dstIndex += rle * ColorBgra.SizeOf;
- x += rle;
- }
- return rleLeftOver;
- }
- private void ExpandUncompressedLine(MemoryBlock dst, int dstIndex, ref TgaHeader header, Stream input, int width, int y, int xoffset, ColorBgra[] palette)
- {
- for (int i = 0; i < width; ++i)
- {
- ColorBgra color = ReadColor(input, header.pixelDepth, palette);
- dst[dstIndex] = color[0];
- dst[1 + dstIndex] = color[1];
- dst[2 + dstIndex] = color[2];
- dst[3 + dstIndex] = color[3];
- dstIndex += 4;
- }
- }
- private ColorBgra ReadColor(Stream input, int pixelDepth, ColorBgra[] palette)
- {
- ColorBgra color;
- switch (pixelDepth)
- {
- case 32:
- {
- long colorInt = Utility.ReadUInt32(input);
- if (colorInt == -1)
- {
- throw new EndOfStreamException();
- }
- color = ColorBgra.FromUInt32((uint)colorInt);
- break;
- }
- case 24:
- {
- int colorInt = Utility.ReadUInt24(input);
- if (colorInt == -1)
- {
- throw new EndOfStreamException();
- }
- color = ColorBgra.FromUInt32((uint)colorInt);
- color.A = 255;
- break;
- }
- case 15:
- case 16:
- {
- int colorWord = Utility.ReadUInt16(input);
- if (colorWord == -1)
- {
- throw new EndOfStreamException();
- }
- color = ColorBgra.FromBgra(
- (byte)((colorWord >> 7) & 0xf8),
- (byte)((colorWord >> 2) & 0xf8),
- (byte)((colorWord & 0x1f) * 8),
- 255);
- break;
- }
- case 8:
- {
- int colorByte = input.ReadByte();
- if (colorByte == -1)
- {
- throw new EndOfStreamException();
- }
- if (colorByte >= palette.Length)
- {
- throw new FormatException("color index was outside the bounds of the palette");
- }
- color = palette[colorByte];
- break;
- }
- default:
- throw new FormatException("colorDepth was not one of {8, 15, 16, 24, 32}");
- }
- return color;
- }
- private void SaveTga(Surface input, Stream output, SavableBitDepths bitDepth, bool rleCompress, ProgressEventHandler progressCallback)
- {
- TgaHeader header = new TgaHeader();
- header.idLength = 0;
- header.cmapType = 0;
- header.imageType = rleCompress ? TgaType.RleRgb : TgaType.Rgb;
- header.cmapIndex = 0;
- header.cmapLength = 0;
- header.cmapEntrySize = 0; // if bpp=8, set this to 24
- header.xOrigin = 0;
- header.yOrigin = 0;
- header.imageWidth = (ushort)input.Width;
- header.imageHeight = (ushort)input.Height;
- header.imageDesc = 0;
- switch (bitDepth)
- {
- case SavableBitDepths.Rgba32:
- header.pixelDepth = 32;
- header.imageDesc |= 8;
- break;
- case SavableBitDepths.Rgb24:
- header.pixelDepth = 24;
- break;
- default:
- throw new InvalidEnumArgumentException("bitDepth", (int)bitDepth, typeof(SavableBitDepths));
- }
- header.Write(output);
- // write palette if doing 8-bit
- // ... todo?
- for (int y = input.Height - 1; y >= 0; --y)
- {
- // non-rle output
- if (rleCompress)
- {
- SaveTgaRowRle(output, input, ref header, y);
- }
- else
- {
- SaveTgaRowRaw(output, input, ref header, y);
- }
- if (progressCallback != null)
- {
- progressCallback(this, new ProgressEventArgs(100.0 * ((double)(input.Height - y) / (double)input.Height)));
- }
- }
- }
- private class TgaPacketStateMachine
- {
- private bool rlePacket = false;
- private ColorBgra[] packetColors = new ColorBgra[128];
- private int packetLength;
- private Stream output;
- private int bitDepth;
- public void Flush()
- {
- byte header = (byte)((rlePacket ? 128 : 0) + (byte)(packetLength - 1));
- output.WriteByte(header);
- int length = (rlePacket ? 1 : packetLength);
- for (int i = 0; i < length; ++i)
- {
- WriteColor(this.output, packetColors[i], this.bitDepth);
- }
- packetLength = 0;
- }
- public void Push(ColorBgra color)
- {
- if (packetLength == 0)
- {
- // Starting a fresh packet.
- rlePacket = false;
- packetColors[0] = color;
- packetLength = 1;
- }
- else if (packetLength == 1)
- {
- // 2nd byte of this packet... decide RLE or non-RLE.
- rlePacket = (color == packetColors[0]);
- packetColors[1] = color;
- packetLength = 2;
- }
- else if (packetLength == packetColors.Length)
- {
- // Packet is full. Start a new one.
- Flush();
- Push(color);
- }
- else if (packetLength >= 2 && rlePacket && color != packetColors[packetLength - 1])
- {
- // We were filling in an RLE packet, and we got a non-repeated color.
- // Emit the current packet and start a new one.
- Flush();
- Push(color);
- }
- else if (packetLength >= 2 && rlePacket && color == packetColors[packetLength - 1])
- {
- // We are filling in an RLE packet, and we got another repeated color.
- // Add the new color to the current packet.
- ++packetLength;
- packetColors[packetLength - 1] = color;
- }
- else if (packetLength >= 2 && !rlePacket && color != packetColors[packetLength - 1])
- {
- // We are filling in a raw packet, and we got another random color.
- // Add the new color to the current packet.
- ++packetLength;
- packetColors[packetLength - 1] = color;
- }
- else if (packetLength >= 2 && !rlePacket && color == packetColors[packetLength - 1])
- {
- // We were filling in a raw packet, but we got a repeated color.
- // Emit the current packet without its last color, and start a
- // new RLE packet that starts with a length of 2.
- --packetLength;
- Flush();
- Push(color);
- Push(color);
- }
- }
- public TgaPacketStateMachine(Stream output, int bitDepth)
- {
- this.output = output;
- this.bitDepth = bitDepth;
- }
- }
- private static void SaveTgaRowRle(Stream output, Surface input, ref TgaHeader header, int y)
- {
- TgaPacketStateMachine machine = new TgaPacketStateMachine(output, header.pixelDepth);
- for (int x = 0; x < input.Width; ++x)
- {
- machine.Push(input[x, y]);
- }
- machine.Flush();
- }
- private static void SaveTgaRowRaw(Stream output, Surface input, ref TgaHeader header, int y)
- {
- for (int x = 0; x < input.Width; ++x)
- {
- ColorBgra color = input[x, y];
- WriteColor(output, color, header.pixelDepth);
- }
- }
- private static void WriteColor(Stream output, ColorBgra color, int bitDepth)
- {
- switch (bitDepth)
- {
- case 24:
- {
- int red = ((color.R * color.A) + (255 * (255 - color.A))) / 255;
- int green = ((color.G * color.A) + (255 * (255 - color.A))) / 255;
- int blue = ((color.B * color.A) + (255 * (255 - color.A))) / 255;
- int colorInt = blue + (green << 8) + (red << 16);
- Utility.WriteUInt24(output, colorInt);
- break;
- }
- case 32:
- Utility.WriteUInt32(output, color.Bgra);
- break;
- }
- }
- public TgaFileType()
- : base("TGA",
- FileTypeFlags.SavesWithProgress |
- FileTypeFlags.SupportsLoading |
- FileTypeFlags.SupportsSaving,
- new string[] { ".tga" })
- {
- }
- }
- }
|