Exemple #1
0
        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);
            }
        }
Exemple #2
0
 /// <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;
 }
Exemple #3
0
        /// <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());
        }
Exemple #4
0
        /// <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());
        }
Exemple #5
0
        /// <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;
        }
Exemple #6
0
        /// <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());
        }
Exemple #7
0
        /// <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);
                }