internal void WriteHeader(BeBinaryWriter imageOutput, ImageDetails imageDetails) { // If the image is indexed, it'll always be a single channel, otherwise PICT is always 3 Channels + Alpha var pictComponents = (imageDetails.IsIndexed ? 1 : 4); // If a source image is 24 bit, return 32 bit // Otherwise 1, 4, 8, 16 and 32 bit images supported. var pictBps = imageDetails.BitsPerPixel == 24 ? 32 : imageDetails.BitsPerPixel; // I've seen all sorts of weird things PackType packType = PackType.PackBits; if (imageDetails.BitsPerPixel == 1) { packedBytes = (imageDetails.Width / 8 + ((imageDetails.Width % 8) != 0 ? 1 : 0)); rowBytes = packedBytes; sourceRowBytes = rowBytes; packType = PackType.PackBits; } if (imageDetails.BitsPerPixel == 2) { packedBytes = (imageDetails.Width / 4 + ((imageDetails.Width % 4) != 0 ? 1 : 0)); rowBytes = packedBytes; sourceRowBytes = rowBytes; packType = PackType.PackBits; } if (imageDetails.BitsPerPixel == 4) { packedBytes = (imageDetails.Width / 2 + ((imageDetails.Width % 2) != 0 ? 1 : 0)); rowBytes = packedBytes; sourceRowBytes = rowBytes; packType = PackType.PackBits; } else if (imageDetails.BitsPerPixel == 8) { packedBytes = (int)(((imageDetails.Width * 8) / imageDetails.BitsPerPixel) + (((imageDetails.Width * 8) % imageDetails.Width) != 0 ? 1 : 0)); rowBytes = imageDetails.Width * 1; sourceRowBytes = rowBytes; packType = PackType.PackBits; } else if (imageDetails.BitsPerPixel == 16) { packedBytes = imageDetails.Width * 2; rowBytes = imageDetails.Width * 2; sourceRowBytes = imageDetails.Width * 2; packType = PackType.PackBits; } else if (imageDetails.BitsPerPixel == 24) { packedBytes = imageDetails.Width * 4; rowBytes = imageDetails.Width * 4; sourceRowBytes = imageDetails.Width * 3; packType = PackType.PackBitsRgb; } else if (imageDetails.BitsPerPixel == 32) { packedBytes = imageDetails.Width * pictComponents; rowBytes = imageDetails.Width * pictComponents; sourceRowBytes = imageDetails.Width * 4; packType = PackType.PackBitsRgb; } if (rowBytes < 8) { packType = PackType.NotPacked; } // TODO: Make 512 byte header optional // Write empty 512-byte header byte[] buf = new byte[Pict.PICT_NULL_HEADER_SIZE]; imageOutput.Write(buf); // Write out the size, leave as 0, this is ok imageOutput.WriteShort(0); // Write image frame (same as image bounds) imageOutput.WriteShort(imageDetails.Top); imageOutput.WriteShort(imageDetails.Left); imageOutput.WriteShort(imageDetails.Bottom); imageOutput.WriteShort(imageDetails.Right); // Write version, version 2 imageOutput.WriteShort(Pict.OP_VERSION); imageOutput.WriteShort(Pict.OP_VERSION_2); // Version 2 HEADER_OP, extended version. imageOutput.WriteShort(Pict.OP_HEADER_OP); imageOutput.WriteInt(Pict.HEADER_V2_EXT); // incl 2 bytes reseverd //imageOutput.WriteShort(FFEE); // FFEF or FFEE //imageOutput.WriteShort(0x0); // Reservered // Original Horizontal Resolution in Pixels / Inch // Image resolution, 72 dpi (long) imageOutput.WriteShort(72); imageOutput.WriteShort(0); // Original Verticale Resolution in Pixels / Inch imageOutput.WriteShort(72); imageOutput.WriteShort(0); // Optimal source rectangle (same as image bounds) // Frame at original resolution imageOutput.WriteShort(imageDetails.Top); imageOutput.WriteShort(imageDetails.Left); imageOutput.WriteShort(imageDetails.Bottom); imageOutput.WriteShort(imageDetails.Right); // Reserved (4 bytes) imageOutput.WriteInt(0); // ------------------ END OF HEADER -------------------- // // This is where things get weird...depending on bpp // Set the clip rectangle imageOutput.WriteShort(Pict.OP_CLIP_RGN); imageOutput.WriteShort(10); imageOutput.WriteShort(imageDetails.Top); // top imageOutput.WriteShort(imageDetails.Left); // left imageOutput.WriteShort(imageDetails.Bottom); imageOutput.WriteShort(imageDetails.Right); if (imageDetails.IsIndexed) { if (imageDetails.BitsPerPixel == 1) { imageOutput.WriteShort(Pict.OP_BITS_RECT); imageOutput.WriteShort(rowBytes); // Write bounds rectangle (same as image bounds) imageOutput.WriteShort(imageDetails.Top); // top imageOutput.WriteShort(imageDetails.Left); // left imageOutput.WriteShort(imageDetails.Bottom); // TODO: Handle overflow? // bottom imageOutput.WriteShort(imageDetails.Right); // right // Source and dest rect (both are same as image bounds) imageOutput.WriteShort(imageDetails.Top); imageOutput.WriteShort(imageDetails.Left); imageOutput.WriteShort(imageDetails.Bottom); imageOutput.WriteShort(imageDetails.Right); // Dest Rect imageOutput.WriteShort(imageDetails.Top); imageOutput.WriteShort(imageDetails.Left); imageOutput.WriteShort(imageDetails.Bottom); imageOutput.WriteShort(imageDetails.Right); // Transfer mode imageOutput.WriteShort(0); imageOutput.Flush(); return; } else { imageOutput.WriteShort(Pict.OP_PACK_BITS_RECT); // Packbits // The offset in bytes from one row of the image to the next. The value must be even, less than $4000, and for best performance it should be a multiple of 4. // The high 2 bits of rowBytes are used as flags. If bit 15 = 1, the data structure pointed to is a PixMap record; otherwise it is a BitMap record. imageOutput.WriteUShort((ushort)(rowBytes | 0x8000)); } } else // RGB Image { // Pixmap operation imageOutput.WriteShort(Pict.OP_DIRECT_BITS_RECT); // 0x9A - sometimes called pict9a // PixMap pointer (always 0x000000FF); imageOutput.WriteInt(0xff); // I see conflicting things about writing out row bytes at this point... imageOutput.WriteUShort((ushort)(rowBytes | 0x8000)); } // Write bounds rectangle (same as image bounds) imageOutput.WriteShort(imageDetails.Top); // top imageOutput.WriteShort(imageDetails.Left); // left imageOutput.WriteShort(imageDetails.Bottom); // TODO: Handle overflow? // bottom imageOutput.WriteShort(imageDetails.Right); // right // PixMap record version // The version number of Color QuickDraw that created this PixMap record. // The value of pmVersion is normally 0. If pmVersion is 4, Color QuickDraw treats the PixMap record's baseAddr field as 32-bit clean. // (All other flags are private.) Most applications never need to set this field. imageOutput.WriteShort(0); // Packing format (always 4: PackBits) // * 0 is default indexed packing. // * 1 is no packing (rowBytes < 8) // * 2 // * 3 // * 4 is default direct packing - run length encoded scan lines by component, red first. imageOutput.WriteShort(packType); // Size of packed data (leave as 0) // The size of the packed image in bytes. When the packType field contains the value 0, this field is always set to 0 imageOutput.WriteInt(0); // Pixmap resolution, 72 dpi //imageOutput.WriteShort(Pict.MAC_DEFAULT_DPI+0.5); imageOutput.WriteShort(72); imageOutput.WriteShort(0); //imageOutput.WriteShort(Pict.MAC_DEFAULT_DPI+0.5); imageOutput.WriteShort(72); imageOutput.WriteShort(0); // Pixel type // The storage format for a pixel image. Indexed pixels are indicated by a value of 0. // Direct pixels are specified by a value of RGBDirect, or 16. // In the PixMap record of the GDevice record (described in the chapter "Graphics Devices") // for a direct device, this field is set to the constant RGBDirect when the screen depth is set. if (imageDetails.IsIndexed) { imageOutput.WriteShort(0); } else { imageOutput.WriteShort(16); } // Pixel size // Pixel depth; that is, the number of bits used to represent a pixel. // Indexed pixels can have sizes of 1, 2, 4, and 8 bits; direct pixel sizes are 16 and 32 bits. imageOutput.WriteShort(pictBps); // Pixel component count (planes) - ie 1 for indexed, 3 or 4 for RGB imageOutput.WriteShort(pictComponents); // Pixel component size /* * The size in bits of each component for a pixel. Color QuickDraw expects that the sizes of all components * are the same, and that the value of the cmpCount field multiplied by the value of the cmpSize field * is less than or equal to the value in the pixelSize field. * For an indexed pixel value, which has only one component, the value of the cmpSize field is the same * as the value of the pixelSize field--that is, 1, 2, 4, or 8. * For direct pixels there are two additional possibilities: * A 16-bit pixel, which has three components, has a cmpSize value * of 5. This leaves an unused high-order bit, which Color QuickDraw sets to 0. * A 32-bit pixel, which has three components (red, green, and blue), has a cmpSize value of 8. * This leaves an unused high-order byte, which Color QuickDraw sets to 0. */ if (imageDetails.IsIndexed) { imageOutput.WriteShort(pictBps); } else if (imageDetails.BitsPerPixel == 16) { imageOutput.WriteShort(5); } else { imageOutput.WriteShort(8); } // PlaneBytes, ignored for now imageOutput.WriteInt(0); // TODO: Allow IndexColorModel? // ColorTable record (for RGB direct pixels, just write 0) // Pixmap Colour Table (seems to always be 0?) imageOutput.WriteInt(0); //imageOutput.WriteInt(0x101F); // Reserved (4 bytes) imageOutput.WriteInt(0); // Write out ColorTable if (imageDetails.IsIndexed) { imageOutput.WriteInt(0); // color seed - Resource ID // Colour Flags Flags. A value of $0000 identifies this as a color table for a pixel map. A value of $8000 identifies this as a color table for an indexed device. imageOutput.WriteShort(0); // Entry count / Size. One less than the number of color specification entries in the rest of this resource. imageOutput.WriteShort((ushort)(imageDetails.Palette.Length - 1)); for (ushort i = 0; i < imageDetails.Palette.Length; i++) { imageOutput.WriteShort(i); // pixel value // Each colour is a 16bit value...scale to short imageOutput.WriteUShort(ScaleQuantumToShort(imageDetails.Palette[i].R)); imageOutput.WriteUShort(ScaleQuantumToShort(imageDetails.Palette[i].G)); imageOutput.WriteUShort(ScaleQuantumToShort(imageDetails.Palette[i].B)); } } // Source and dest rect (both are same as image bounds) imageOutput.WriteShort(imageDetails.Top); imageOutput.WriteShort(imageDetails.Left); imageOutput.WriteShort(imageDetails.Bottom); imageOutput.WriteShort(imageDetails.Right); // Dest Rect imageOutput.WriteShort(imageDetails.Top); imageOutput.WriteShort(imageDetails.Left); imageOutput.WriteShort(imageDetails.Bottom); imageOutput.WriteShort(imageDetails.Right); // Transfer mode if (imageDetails.IsIndexed) { imageOutput.WriteShort(0); } else { imageOutput.WriteShort(/*QuickDraw.SRC_COPY*/ 0x40); } imageOutput.Flush(); // Now write image data }