public Document Load(Stream input) { LoadPSPFile(input); if (this.imageAttributes == null || this.layerBlock == null) { throw new FormatException(Properties.Resources.InvalidPSPFile); } if (this.imageAttributes.BitDepth <= 8 && this.globalPalette == null) { throw new FormatException(Properties.Resources.ColorPaletteNotFound); } LayerInfoChunk[] infoChunks = this.layerBlock.LayerInfo; int layerCount = infoChunks.Length; // Some PSP files may have layers that are larger than the dimensions stored in the image attributes. int maxWidth = this.imageAttributes.Width; int maxHeight = this.imageAttributes.Height; for (int i = 0; i < layerCount; i++) { Rectangle savedBounds = infoChunks[i].saveRect; if (savedBounds.Width > maxWidth) { maxWidth = savedBounds.Width; } if (savedBounds.Height > maxHeight) { maxHeight = savedBounds.Height; } } if (maxWidth <= 0 || maxHeight <= 0) { throw new FormatException(Properties.Resources.InvalidDocumentDimensions); } Document doc = new Document(maxWidth, maxHeight); if (this.imageAttributes.ResValue > 0.0) { switch (this.imageAttributes.ResUnit) { case ResolutionMetric.Inch: doc.DpuUnit = MeasurementUnit.Inch; doc.DpuX = doc.DpuY = this.imageAttributes.ResValue; break; case ResolutionMetric.Centimeter: doc.DpuUnit = MeasurementUnit.Centimeter; doc.DpuX = doc.DpuY = this.imageAttributes.ResValue; break; default: break; } } short transIndex = -1; if (this.imageAttributes.BitDepth < 24 && this.extData != null) { byte[] data; if (this.extData.Values.TryGetValue(PSPExtendedDataID.TransparencyIndex, out data)) { transIndex = BitConverter.ToInt16(data, 0); } } LayerBitmapInfoChunk[] bitmapInfoChunks = this.layerBlock.LayerBitmapInfo; for (int i = 0; i < layerCount; i++) { LayerInfoChunk info = infoChunks[i]; BitmapLayer layer = new BitmapLayer(doc.Width, doc.Height) { Name = info.name, Opacity = info.opacity }; UserBlendOp blendOp = BlendModetoBlendOp(info.blendMode); layer.SetBlendOp(blendOp); layer.Visible = (info.layerFlags & PSPLayerProperties.Visible) == PSPLayerProperties.Visible; Rectangle saveRect = info.saveRect; if (!saveRect.IsEmpty) { LayerBitmapInfoChunk bitmapInfo = bitmapInfoChunks[i]; if (bitmapInfo.bitmapCount == 1) { new UnaryPixelOps.SetAlphaChannelTo255().Apply(layer.Surface, saveRect); } int alphaIndex = 0; int bitDepth = this.imageAttributes.BitDepth; int bytesPerPixel = 1; int stride = saveRect.Width; byte[] expandedPalette = null; switch (bitDepth) { case 48: bytesPerPixel = 2; stride *= 2; break; case 4: case 1: expandedPalette = ExpandPackedPalette(saveRect, bitmapInfo, bitDepth); break; } unsafe { int palIdx = 0; NativeStructs.RGBQUAD entry; Surface surface = layer.Surface; for (int y = saveRect.Top; y < saveRect.Bottom; y++) { ColorBgra *ptr = surface.GetPointAddressUnchecked(saveRect.Left, y); ColorBgra *endPtr = ptr + saveRect.Width; int index = ((y - saveRect.Top) * stride); while (ptr < endPtr) { switch (bitDepth) { case 48: for (int ci = 0; ci < bitmapInfo.channelCount; ci++) { ChannelSubBlock ch = bitmapInfo.channels[ci]; if (ch.bitmapType == PSPDIBType.Image) { ushort col = (ushort)(ch.channelData[index] | (ch.channelData[index + 1] << 8)); // PSP format is always little endian byte clamped = (byte)((col * 255) / 65535); switch (ch.channelType) { case PSPChannelType.Red: ptr->R = clamped; break; case PSPChannelType.Green: ptr->G = clamped; break; case PSPChannelType.Blue: ptr->B = clamped; break; } } else if (ch.bitmapType == PSPDIBType.TransparencyMask) { ptr->A = ch.channelData[alphaIndex]; alphaIndex++; } } break; case 24: for (int ci = 0; ci < bitmapInfo.channelCount; ci++) { ChannelSubBlock ch = bitmapInfo.channels[ci]; if (ch.bitmapType == PSPDIBType.Image) { switch (ch.channelType) { case PSPChannelType.Red: ptr->R = ch.channelData[index]; break; case PSPChannelType.Green: ptr->G = ch.channelData[index]; break; case PSPChannelType.Blue: ptr->B = ch.channelData[index]; break; } } else if (ch.bitmapType == PSPDIBType.TransparencyMask) { ptr->A = ch.channelData[index]; } } break; case 8: for (int ci = 0; ci < bitmapInfo.channelCount; ci++) { ChannelSubBlock ch = bitmapInfo.channels[ci]; palIdx = ch.channelData[index]; entry = this.globalPalette.entries[palIdx]; switch (ch.bitmapType) { case PSPDIBType.Image: ptr->R = entry.rgbRed; ptr->G = entry.rgbGreen; ptr->B = entry.rgbBlue; if (palIdx == transIndex) { ptr->A = 0; } break; case PSPDIBType.TransparencyMask: ptr->A = entry.rgbRed; break; } } break; case 4: case 1: palIdx = expandedPalette[index]; entry = this.globalPalette.entries[palIdx]; ptr->R = entry.rgbRed; ptr->G = entry.rgbGreen; ptr->B = entry.rgbBlue; if (palIdx == transIndex) { ptr->A = 0; } else if ((bitmapInfo.bitmapCount == 2) && bitmapInfo.channels[1].bitmapType == PSPDIBType.TransparencyMask) { ptr->A = bitmapInfo.channels[1].channelData[index]; } break; default: throw new FormatException(string.Format(Properties.Resources.UnsupportedBitDepth, bitDepth)); } ptr++; index += bytesPerPixel; } } } } #if DEBUG using (Bitmap temp = layer.Surface.CreateAliasedBitmap()) { } #endif doc.Layers.Add(layer); } string creatorData = SerializeToBase64(this.creator); doc.Metadata.SetUserValue(PSPCreatorMetaData, creatorData); return(doc); }
public void Save(Document input, Stream output, CompressionFormats format, Surface scratchSurface, ushort majorVersion, ProgressEventHandler callback) { if (majorVersion == PSPConstants.majorVersion5 && input.Layers.Count > 64) { throw new FormatException(string.Format(Properties.Resources.MaxLayersFormat, 64)); } else if ((majorVersion == PSPConstants.majorVersion6 || majorVersion == PSPConstants.majorVersion7) && input.Layers.Count > 100) { throw new FormatException(string.Format(Properties.Resources.MaxLayersFormat, 100)); } using (BinaryWriterEx writer = new BinaryWriterEx(output, false)) { this.fileHeader = new FileHeader(majorVersion); this.imageAttributes = new GeneralImageAttributes( input.Width, input.Height, CompressionFromTokenFormat(format), 0, input.Layers.Count, majorVersion); switch (input.DpuUnit) { case MeasurementUnit.Centimeter: this.imageAttributes.ResValue = input.DpuX; this.imageAttributes.ResUnit = ResolutionMetric.Centimeter; break; case MeasurementUnit.Inch: this.imageAttributes.ResValue = input.DpuX; this.imageAttributes.ResUnit = ResolutionMetric.Inch; break; } this.totalProgress = 0; this.doneProgress = 0; bool flatImage = true; foreach (Layer item in input.Layers) { BitmapLayer layer = (BitmapLayer)item; Rectangle rect = PSPUtil.GetImageSaveRectangle(layer.Surface); if (!rect.IsEmpty) { if (LayerHasTransparency(layer.Surface, rect)) { this.totalProgress += 4; flatImage = false; } else { this.totalProgress += 3; } } } if (flatImage) { this.imageAttributes.SetGraphicContentFlag(PSPGraphicContents.FlatImage); } if (majorVersion > PSPConstants.majorVersion5) { CreateCompositeImageBlock(input, scratchSurface, callback, majorVersion); } else { CreateThumbnailBlock(input, scratchSurface, callback, majorVersion); } int layerCount = input.Layers.Count; LayerInfoChunk[] layerInfoChunks = new LayerInfoChunk[layerCount]; LayerBitmapInfoChunk[] layerBitmapChunks = new LayerBitmapInfoChunk[layerCount]; for (int i = 0; i < layerCount; i++) { BitmapLayer layer = (BitmapLayer)input.Layers[i]; Rectangle savedBounds = PSPUtil.GetImageSaveRectangle(layer.Surface); LayerInfoChunk infoChunk = new LayerInfoChunk(layer, BlendOptoBlendMode(layer.BlendOp), savedBounds, majorVersion); int channelCount = 3; int bitmapCount = 1; if (LayerHasTransparency(layer.Surface, savedBounds)) { channelCount = 4; bitmapCount = 2; } LayerBitmapInfoChunk biChunk = new LayerBitmapInfoChunk(bitmapCount, channelCount) { channels = SplitImageChannels(layer.Surface, savedBounds, channelCount, majorVersion, false, callback) }; if (majorVersion <= PSPConstants.majorVersion5) { infoChunk.v5BitmapCount = biChunk.bitmapCount; infoChunk.v5ChannelCount = biChunk.channelCount; } layerInfoChunks[i] = infoChunk; layerBitmapChunks[i] = biChunk; } this.layerBlock = new LayerBlock(layerInfoChunks, layerBitmapChunks); string creatorData = input.Metadata.GetUserValue(PSPCreatorMetaData); if (!string.IsNullOrEmpty(creatorData)) { this.creator = DeserializeFromBase64 <CreatorBlock>(creatorData); } else { this.creator = new CreatorBlock(); } writer.Write(PSPFileSig); this.fileHeader.Save(writer); this.imageAttributes.Save(writer); this.creator.Save(writer); if (this.compImage != null) { this.compImage.Save(writer); } else if (this.v5Thumbnail != null) { this.v5Thumbnail.Save(writer); } this.layerBlock.Save(writer, majorVersion); } }
private static unsafe byte[] ExpandPackedPalette(Rectangle saveRect, LayerBitmapInfoChunk bitmap, int bitDepth) { int height = saveRect.Height; int width = saveRect.Width; byte[] image = new byte[width * height]; int bpp, shift; switch (bitDepth) { case 4: bpp = 2; shift = 1; break; case 1: bpp = 8; shift = 3; break; default: throw new ArgumentOutOfRangeException("bitDepth", string.Format("Bit depth value of {0} is not supported, value must be 1 or 4.", bitDepth)); } fixed(byte *ptr = image, dPtr = bitmap.channels[0].channelData) { int srcStride = width / bpp; for (int y = 0; y < height; y++) { byte *src = dPtr + (y * srcStride); byte *dst = ptr + (y * width); for (int x = 0; x < width; x++) { byte data = src[x >> shift]; switch (bitDepth) { case 4: if ((x & 1) == 1) // odd column { *dst = (byte)(data & 0x0f); } else { *dst = (byte)(data >> 4); } break; case 1: int mask = x & 7; // extract the palette bit for the current pixel. *dst = (byte)((data & (128 >> mask)) >> (7 - mask)); break; } dst++; } } } return(image); }