//this is where all the main layer information is parsed private void parseLayerInfo() { Console.Out.WriteLine("parsing layer info"); //get the length of the layers data uint lengthOfLayersData = bytes.getUI32(); //and the number of layers short layerCount = bytes.getI16(); traceProperty("number of layers", layerCount); //if the layers count is negative, it maens there is something special //about the alpha channel if (layerCount < 0) { layerCount *= -1; //throw new Exception("negative layers not implemented yet"); } //numberOfLayers is a uint numberOfLayers = (uint)layerCount; //cycle through each layer and parse it for (uint i = 0; i < layerCount; i++) { //using the builder pattern here to avoid a massive constructor Layer layer = new Layer(); //doing the parsing here rather than delegating it to the layer class for readability //think these are ints layer.top = bytes.getI32(); layer.left = bytes.getI32(); layer.bottom = bytes.getI32(); layer.right = bytes.getI32(); //although the channels are stored in a different section, the number //of channels is stored here layer.numberOfChannels = bytes.getUI16(); layer.hasTransparency = layer.numberOfChannels > 3; //parse the channels info, (not actually the channels yet/0 for (uint a = 0; a < layer.numberOfChannels; a++) { LayerChannelData channelData = new LayerChannelData(); channelData.ID = bytes.getI16(); channelData.channelDataLength = bytes.getUI32(); layer.channels.Add(channelData); } layer.signiture = bytes.getString(4); if (layer.signiture != "8BIM") { throw new Exception("Encountered layer with signiture: " + layer.signiture); } //options like the blendModeKey arent used, but still parsed bytes.skipBytes(4); //between 0-255 layer.opacity = bytes.getByte(); //the base bytes.skipBytes(1); //the flags are booleans which are stored as single bits in one byte byte flags = bytes.getByte(); //checked in libpsd, its a bit cryptic, but looks like 0 is true, 1 is false layer.visible = (flags & 2) == 0; //padding bytes.skipBytes(1); //the extra data fields are tags found at the end of the layer //and are very similar to image resource blocks uint extraDataFieldLength = bytes.getUI32(); //end of the whole layer, i think uint possibleEnd = (uint)bytes.Position + extraDataFieldLength; //parse the layer mask, dont think this is implemented yet parseLayerMask(layer); //parse the blending ranges, dont think this is implemented yet parseLayerBlendingRanges(layer); //read the name of the layer, the name also exists as a unicode name //in an extra tag block layer.name = bytes.getPascalString(4); //cycle through the extra tags while (bytes.Position != possibleEnd) { if (bytes.Position > possibleEnd) { throw new Exception("overflow when parsing extra layer tags"); } String sig = bytes.getString(4); if (sig != "8BIM") { //really screwed up here throw new Exception("unknown sig for layer tag: " + sig); } //get the tag name and the tag length String tagName = bytes.getString(4); int tagLength = bytes.getI32(); //the tag length is padded to an even number if (tagLength % 2 == 1) { tagLength++; } uint tagEnd = (uint)(bytes.Position + tagLength); //i dont like giving 'bytes' to the layer, but im resetting the position afterwards //so it should be fine. //parse the tag layer.parseAdditionalLayerInfo(tagName, tagLength, bytes); //make sure that we are at the start of the next tag, bytes.Position = tagEnd; } //make sure we are at the end of the section bytes.Position = possibleEnd; layers.Add(layer); } }
internal void parseImageChannelData(ByteArray bytes) { Console.Out.WriteLine("layer: " + name); //dont want to render folders or invalid bounds if (type != Layer.TYPE_NORMAL || bottom <= top || right <= left) { return; } image = new Bitmap(right - left, bottom - top, PixelFormat.Format32bppArgb); //cycle through the layers, parsing the bytes, and decoding if necessary foreach (LayerChannelData channel in channels) { //get the next channel pos, so if something goes wrong parsing //this channel, it wont screw up the next one uint nextChannelPos = (uint)(bytes.Position + channel.channelDataLength); if (channel.ID < -1) { //skipping user supplied masks bytes.Position = nextChannelPos; continue; } //lets hope bytes.position is in the right place uint compressionMethod = bytes.getUI16(); //if it needs decompressing switch (compressionMethod) { case 0: //not compressed //get the data and store it in the individual channel objects //-2 is for the 2 bytes taken up by the compression type channel.data = bytes.getBytes((uint)(image.Width * image.Height)); channel.data.Position = 0; break; case 1: //rle channel.data = new ByteArray(new byte[0]); //each scan line is compressed seperately, so we need to get the //length of each encoded line List <uint> lineLengths = new List <uint>(image.Height); //then we can cycle though each line and decode it for (int i = 0; i < image.Height; ++i) { lineLengths.Add(bytes.getUI16()); } //now that the lines have been decoded, we can write them back to //the channel MemoryStream data = new MemoryStream(); for (int i = 0; i < image.Height; ++i) { ByteArray line = bytes.getBytes(lineLengths[i]); byte[] unrle = ByteArray.unRLE(line); data.Write(unrle, 0, unrle.Length); } channel.data = new ByteArray(data.ToArray()); break; case 2: case 3: //cant handle these compression methods, //going to skip for now return; } //just in case the rle doesnt reset the position properly channel.data.Position = 0; bytes.Position = nextChannelPos; } //will be null if it doesnt exist LayerChannelData alphaChannel = getChannel(LayerChannelData.TRANSPARENCY_MASK); LayerChannelData redChannel = getChannel(LayerChannelData.RED); LayerChannelData greenChannel = getChannel(LayerChannelData.GREEN); LayerChannelData blueChannel = getChannel(LayerChannelData.BLUE); for (uint j = 0; j < image.Height; j++) { for (uint k = 0; k < image.Width; k++) { //if there is alpha we have to use setPixel32 as there are 4 bytes //to worry about if (alphaChannel != null) { image.SetPixel((int)k, (int)j, Color.FromArgb(alphaChannel.data.getByte(), redChannel.data.getByte(), greenChannel.data.getByte(), blueChannel.data.getByte())); } else { image.SetPixel((int)k, (int)j, Color.FromArgb(redChannel.data.getByte(), greenChannel.data.getByte(), blueChannel.data.getByte())); } } } //now sort out out the mask, cant do masks very well if (mask != null && mask.right > mask.left && mask.top < mask.bottom) { //this represents the bounds of the image Rectangle realRect = new Rectangle(left, top, right - left, bottom - top); //this represents the bounds of the mask Rectangle maskRect = new Rectangle(mask.left, mask.top, mask.right - mask.left, mask.bottom - mask.top); //we combine them to get the new bounds Rectangle insideRect = new Rectangle(); insideRect.X = Math.Max(realRect.X, maskRect.X); insideRect.Y = Math.Max(realRect.Y, maskRect.Y); insideRect.Width = Math.Min(realRect.Right, maskRect.Right) - insideRect.X; insideRect.Height = Math.Min(realRect.Bottom, maskRect.Bottom) - insideRect.Y; image = image.Clone(insideRect, PixelFormat.Format32bppArgb); left = insideRect.X; right = insideRect.Right; top = insideRect.Y; bottom = insideRect.Bottom; } }