private static int GetBytesPerPixel(ColorSpaceDetails details) { switch (details) { case DeviceGrayColorSpaceDetails deviceGray: return(1); case CalGrayColorSpaceDetails calGray: return(1); case DeviceRgbColorSpaceDetails deviceRgb: return(3); case CalRGBColorSpaceDetails calRgb: return(3); case DeviceCmykColorSpaceDetails deviceCmyk: return(4); case IndexedColorSpaceDetails indexed: return(GetBytesPerPixel(indexed.BaseColorSpaceDetails)); case ICCBasedColorSpaceDetails iccBased: // Currently PdfPig only supports the 'Alternate' color space of ICCBasedColorSpaceDetails return(GetBytesPerPixel(iccBased.AlternateColorSpaceDetails)); default: return(1); } }
/// <summary> /// Creates a new <see cref="XObjectImage"/>. /// </summary> internal XObjectImage(PdfRectangle bounds, int widthInSamples, int heightInSamples, int bitsPerComponent, ColorSpace?colorSpace, bool isJpxEncoded, bool isImageMask, RenderingIntent renderingIntent, bool interpolate, IReadOnlyList <decimal> decode, DictionaryToken imageDictionary, IReadOnlyList <byte> rawBytes, Lazy <IReadOnlyList <byte> > bytes, ColorSpaceDetails colorSpaceDetails) { Bounds = bounds; WidthInSamples = widthInSamples; HeightInSamples = heightInSamples; BitsPerComponent = bitsPerComponent; ColorSpace = colorSpace; IsJpxEncoded = isJpxEncoded; IsImageMask = isImageMask; RenderingIntent = renderingIntent; Interpolate = interpolate; Decode = decode; ImageDictionary = imageDictionary ?? throw new ArgumentNullException(nameof(imageDictionary)); RawBytes = rawBytes; ColorSpaceDetails = colorSpaceDetails; bytesFactory = bytes; }
/// <summary> /// Converts the output bytes (if available) of <see cref="IPdfImage.TryGetBytes"/> /// to actual pixel values using the <see cref="IPdfImage.ColorSpaceDetails"/>. For most images this doesn't /// change the data but for <see cref="ColorSpace.Indexed"/> it will convert the bytes which are indexes into the /// real pixel data into the real pixel data. /// </summary> public static byte[] Convert(ColorSpaceDetails details, IReadOnlyList <byte> decoded, int bitsPerComponent, int imageWidth, int imageHeight) { if (decoded == null) { return(EmptyArray <byte> .Instance); } if (details == null) { return(decoded.ToArray()); } switch (details) { case IndexedColorSpaceDetails indexed: if (bitsPerComponent != 8) { // To ease unwrapping further below the indices are unpacked to occupy a single byte each decoded = UnpackIndices(decoded, bitsPerComponent); // Remove padding bytes when the stride width differs from the image width var stride = (imageWidth * bitsPerComponent + 7) / 8; var strideWidth = stride * (8 / bitsPerComponent); if (strideWidth != imageWidth) { decoded = RemoveStridePadding(decoded.ToArray(), strideWidth, imageWidth, imageHeight); } } return(UnwrapIndexedColorSpaceBytes(indexed, decoded)); } return(decoded.ToArray()); }
/// <summary> /// Converts the output bytes (if available) of <see cref="IPdfImage.TryGetBytes"/> /// to actual pixel values using the <see cref="IPdfImage.ColorSpaceDetails"/>. For most images this doesn't /// change the data but for <see cref="ColorSpace.Indexed"/> it will convert the bytes which are indexes into the /// real pixel data into the real pixel data. /// </summary> public static byte[] Convert(ColorSpaceDetails details, IReadOnlyList <byte> decoded, int bitsPerComponent, int imageWidth, int imageHeight) { if (decoded == null) { return(EmptyArray <byte> .Instance); } if (details == null) { return(decoded.ToArray()); } if (bitsPerComponent != 8) { // Unpack components such that they occupy one byte each decoded = UnpackComponents(decoded, bitsPerComponent); } // Remove padding bytes when the stride width differs from the image width var bytesPerPixel = details is IndexedColorSpaceDetails ? 1 : GetBytesPerPixel(details); var strideWidth = decoded.Count / imageHeight / bytesPerPixel; if (strideWidth != imageWidth) { decoded = RemoveStridePadding(decoded.ToArray(), strideWidth, imageWidth, imageHeight, bytesPerPixel); } // In case of indexed color space images, unwrap indices to actual pixel component values if (details is IndexedColorSpaceDetails indexed) { decoded = UnwrapIndexedColorSpaceBytes(indexed, decoded); // Use the base color space in potential further decoding details = indexed.BaseColorSpaceDetails; } if (details is CalRGBColorSpaceDetails calRgb) { decoded = TransformToRGB(calRgb, decoded); } if (details is CalGrayColorSpaceDetails calGray) { decoded = TransformToRgbGrayScale(calGray, decoded); } return(decoded.ToArray()); }
/// <summary> /// Create a new <see cref="InlineImage"/>. /// </summary> internal InlineImage(PdfRectangle bounds, int widthInSamples, int heightInSamples, int bitsPerComponent, bool isImageMask, RenderingIntent renderingIntent, bool interpolate, ColorSpace?colorSpace, IReadOnlyList <decimal> decode, IReadOnlyList <byte> bytes, IReadOnlyList <IFilter> filters, DictionaryToken streamDictionary, ColorSpaceDetails colorSpaceDetails) { Bounds = bounds; WidthInSamples = widthInSamples; HeightInSamples = heightInSamples; ColorSpace = colorSpace; Decode = decode; BitsPerComponent = bitsPerComponent; IsImageMask = isImageMask; RenderingIntent = renderingIntent; Interpolate = interpolate; ImageDictionary = streamDictionary; RawBytes = bytes; ColorSpaceDetails = colorSpaceDetails; var supportsFilters = true; foreach (var filter in filters) { if (!filter.IsSupported) { supportsFilters = false; break; } } bytesFactory = supportsFilters ? new Lazy <IReadOnlyList <byte> >(() => { var b = bytes.ToArray(); for (var i = 0; i < filters.Count; i++) { var filter = filters[i]; b = filter.Decode(b, streamDictionary, i); } return(b); }) : null; }
/// <summary> /// Converts the output bytes (if available) of <see cref="IPdfImage.TryGetBytes"/> /// to actual pixel values using the <see cref="IPdfImage.ColorSpaceDetails"/>. For most images this doesn't /// change the data but for <see cref="ColorSpace.Indexed"/> it will convert the bytes which are indexes into the /// real pixel data into the real pixel data. /// </summary> public static byte[] Convert(ColorSpaceDetails details, IReadOnlyList <byte> decoded) { if (decoded == null) { return(EmptyArray <byte> .Instance); } if (details == null) { return(decoded.ToArray()); } switch (details) { case IndexedColorSpaceDetails indexed: return(UnwrapIndexedColorSpaceBytes(indexed, decoded)); } return(decoded.ToArray()); }
/// <summary> /// Create a new <see cref="ICCBasedColorSpaceDetails"/>. /// </summary> internal ICCBasedColorSpaceDetails(int numberOfColorComponents, [CanBeNull] ColorSpaceDetails alternateColorSpaceDetails, [CanBeNull] IReadOnlyList <decimal> range, [CanBeNull] XmpMetadata metadata) : base(ColorSpace.ICCBased) { if (numberOfColorComponents != 1 && numberOfColorComponents != 3 && numberOfColorComponents != 4) { throw new ArgumentOutOfRangeException(nameof(numberOfColorComponents), "must be 1, 3 or 4"); } NumberOfColorComponents = numberOfColorComponents; AlternateColorSpaceDetails = alternateColorSpaceDetails ?? (NumberOfColorComponents == 1 ? (ColorSpaceDetails)DeviceGrayColorSpaceDetails.Instance : NumberOfColorComponents == 3 ? (ColorSpaceDetails)DeviceRgbColorSpaceDetails.Instance : (ColorSpaceDetails)DeviceCmykColorSpaceDetails.Instance); BaseType = AlternateColorSpaceDetails.BaseType; Range = range ?? Enumerable.Range(0, numberOfColorComponents).Select(x => new[] { 0.0m, 1.0m }).SelectMany(x => x).ToList(); if (Range.Count != 2 * numberOfColorComponents) { throw new ArgumentOutOfRangeException(nameof(range), range, $"Must consist of exactly {2 * numberOfColorComponents } (2 x NumberOfColorComponents), but was passed {range.Count }"); } Metadata = metadata; }
public static ColorSpaceDetails GetColorSpaceDetails(ColorSpace?colorSpace, DictionaryToken imageDictionary, IPdfTokenScanner scanner, IResourceStore resourceStore, ILookupFilterProvider filterProvider, bool cannotRecurse = false) { if (imageDictionary.GetObjectOrDefault(NameToken.ImageMask, NameToken.Im) != null || filterProvider.GetFilters(imageDictionary, scanner).OfType <CcittFaxDecodeFilter>().Any()) { if (cannotRecurse) { return(UnsupportedColorSpaceDetails.Instance); } var colorSpaceDetails = GetColorSpaceDetails(colorSpace, imageDictionary.Without(NameToken.Filter).Without(NameToken.F), scanner, resourceStore, filterProvider, true); var decodeRaw = imageDictionary.GetObjectOrDefault(NameToken.Decode, NameToken.D) as ArrayToken ?? new ArrayToken(EmptyArray <IToken> .Instance); var decode = decodeRaw.Data.OfType <NumericToken>().Select(x => x.Data).ToArray(); return(IndexedColorSpaceDetails.Stencil(colorSpaceDetails, decode)); } if (!colorSpace.HasValue) { return(UnsupportedColorSpaceDetails.Instance); } switch (colorSpace.Value) { case ColorSpace.DeviceGray: return(DeviceGrayColorSpaceDetails.Instance); case ColorSpace.DeviceRGB: return(DeviceRgbColorSpaceDetails.Instance); case ColorSpace.DeviceCMYK: return(DeviceCmykColorSpaceDetails.Instance); case ColorSpace.CalGray: { if (!TryGetColorSpaceArray(imageDictionary, resourceStore, scanner, out var colorSpaceArray) || colorSpaceArray.Length != 2) { return(UnsupportedColorSpaceDetails.Instance); } var first = colorSpaceArray[0] as NameToken; if (first == null || !ColorSpaceMapper.TryMap(first, resourceStore, out var innerColorSpace) || innerColorSpace != ColorSpace.CalGray) { return(UnsupportedColorSpaceDetails.Instance); } var second = colorSpaceArray[1]; // WhitePoint is required if (!DirectObjectFinder.TryGet(second, scanner, out DictionaryToken dictionaryToken) || !dictionaryToken.TryGet(NameToken.WhitePoint, scanner, out ArrayToken whitePointToken)) { return(UnsupportedColorSpaceDetails.Instance); } var whitePoint = whitePointToken.Data.OfType <NumericToken>().Select(x => x.Data).ToList(); // BlackPoint is optional IReadOnlyList <decimal> blackPoint = null; if (dictionaryToken.TryGet(NameToken.BlackPoint, scanner, out ArrayToken blackPointToken)) { blackPoint = blackPointToken.Data.OfType <NumericToken>().Select(x => x.Data).ToList(); } // Gamma is optional decimal?gamma = null; if (dictionaryToken.TryGet(NameToken.Gamma, scanner, out NumericToken gammaToken)) { gamma = gammaToken.Data; } return(new CalGrayColorSpaceDetails(whitePoint, blackPoint, gamma)); } case ColorSpace.CalRGB: { if (!TryGetColorSpaceArray(imageDictionary, resourceStore, scanner, out var colorSpaceArray) || colorSpaceArray.Length != 2) { return(UnsupportedColorSpaceDetails.Instance); } var first = colorSpaceArray[0] as NameToken; if (first == null || !ColorSpaceMapper.TryMap(first, resourceStore, out var innerColorSpace) || innerColorSpace != ColorSpace.CalRGB) { return(UnsupportedColorSpaceDetails.Instance); } var second = colorSpaceArray[1]; // WhitePoint is required if (!DirectObjectFinder.TryGet(second, scanner, out DictionaryToken dictionaryToken) || !dictionaryToken.TryGet(NameToken.WhitePoint, scanner, out ArrayToken whitePointToken)) { return(UnsupportedColorSpaceDetails.Instance); } var whitePoint = whitePointToken.Data.OfType <NumericToken>().Select(x => x.Data).ToList(); // BlackPoint is optional IReadOnlyList <decimal> blackPoint = null; if (dictionaryToken.TryGet(NameToken.BlackPoint, scanner, out ArrayToken blackPointToken)) { blackPoint = blackPointToken.Data.OfType <NumericToken>().Select(x => x.Data).ToList(); } // Gamma is optional IReadOnlyList <decimal> gamma = null; if (dictionaryToken.TryGet(NameToken.Gamma, scanner, out ArrayToken gammaToken)) { gamma = gammaToken.Data.OfType <NumericToken>().Select(x => x.Data).ToList(); } // Matrix is optional IReadOnlyList <decimal> matrix = null; if (dictionaryToken.TryGet(NameToken.Matrix, scanner, out ArrayToken matrixToken)) { matrix = matrixToken.Data.OfType <NumericToken>().Select(x => x.Data).ToList(); } return(new CalRGBColorSpaceDetails(whitePoint, blackPoint, gamma, matrix)); } case ColorSpace.Lab: return(UnsupportedColorSpaceDetails.Instance); case ColorSpace.ICCBased: { if (!TryGetColorSpaceArray(imageDictionary, resourceStore, scanner, out var colorSpaceArray) || colorSpaceArray.Length != 2) { return(UnsupportedColorSpaceDetails.Instance); } var first = colorSpaceArray[0] as NameToken; if (first == null || !ColorSpaceMapper.TryMap(first, resourceStore, out var innerColorSpace) || innerColorSpace != ColorSpace.ICCBased) { return(UnsupportedColorSpaceDetails.Instance); } var second = colorSpaceArray[1]; // N is required if (!DirectObjectFinder.TryGet(second, scanner, out StreamToken streamToken) || !streamToken.StreamDictionary.TryGet(NameToken.N, scanner, out NumericToken numeric)) { return(UnsupportedColorSpaceDetails.Instance); } // Alternate is optional ColorSpaceDetails alternateColorSpaceDetails = null; if (streamToken.StreamDictionary.TryGet(NameToken.Alternate, out NameToken alternateColorSpaceNameToken) && ColorSpaceMapper.TryMap(alternateColorSpaceNameToken, resourceStore, out var alternateColorSpace)) { alternateColorSpaceDetails = GetColorSpaceDetails(alternateColorSpace, imageDictionary, scanner, resourceStore, filterProvider, true); } // Range is optional IReadOnlyList <decimal> range = null; if (streamToken.StreamDictionary.TryGet(NameToken.Range, scanner, out ArrayToken arrayToken)) { range = arrayToken.Data.OfType <NumericToken>().Select(x => x.Data).ToList(); } // Metadata is optional XmpMetadata metadata = null; if (streamToken.StreamDictionary.TryGet(NameToken.Metadata, scanner, out StreamToken metadataStream)) { metadata = new XmpMetadata(metadataStream, filterProvider, scanner); } return(new ICCBasedColorSpaceDetails(numeric.Int, alternateColorSpaceDetails, range, metadata)); } case ColorSpace.Indexed: { if (cannotRecurse) { return(UnsupportedColorSpaceDetails.Instance); } if (!TryGetColorSpaceArray(imageDictionary, resourceStore, scanner, out var colorSpaceArray) || colorSpaceArray.Length != 4) { // Error instead? return(UnsupportedColorSpaceDetails.Instance); } var first = colorSpaceArray[0] as NameToken; if (first == null || !ColorSpaceMapper.TryMap(first, resourceStore, out var innerColorSpace) || innerColorSpace != ColorSpace.Indexed) { return(UnsupportedColorSpaceDetails.Instance); } var second = colorSpaceArray[1]; ColorSpaceDetails baseDetails; if (DirectObjectFinder.TryGet(second, scanner, out NameToken baseColorSpaceNameToken) && ColorSpaceMapper.TryMap(baseColorSpaceNameToken, resourceStore, out var baseColorSpaceName)) { baseDetails = GetColorSpaceDetails( baseColorSpaceName, imageDictionary, scanner, resourceStore, filterProvider, true); } else if (DirectObjectFinder.TryGet(second, scanner, out ArrayToken baseColorSpaceArrayToken) && baseColorSpaceArrayToken.Length > 0 && baseColorSpaceArrayToken[0] is NameToken baseColorSpaceArrayNameToken && ColorSpaceMapper.TryMap(baseColorSpaceArrayNameToken, resourceStore, out var baseColorSpaceArrayColorSpace)) { var pseudoImageDictionary = new DictionaryToken( new Dictionary <NameToken, IToken> { { NameToken.ColorSpace, baseColorSpaceArrayToken } }); baseDetails = GetColorSpaceDetails( baseColorSpaceArrayColorSpace, pseudoImageDictionary, scanner, resourceStore, filterProvider, true); }