public void RegionScale()
        {
            var filename = @"C:\JP2Cache\vdc_0000000388E8.0x000008";
            var request  = new ImageRequest
                           (
                "",
                new ImageRegion(ImageRegionMode.Region, 2048f, 0f, 9f, 1024f),
                new ImageSize(ImageSizeMode.Distort, 1, 3, 0),
                new ImageRotation(0, false),
                ImageQuality.@default,
                TremendousIIIF.Common.ImageFormat.jpg
                           );
            var q = new C.ImageQuality();

            (var state, var img) = J2KExpander.ExpandRegion(null, Log, new Uri(filename), request, false, q);
            using (img)
            {
                Assert.AreEqual(3, img.Width);
                Assert.AreEqual(342, img.Height);
            }
        }
        /// <summary>
        /// Process image pipeline
        /// <para>Region THEN Size THEN Rotation THEN Quality THEN Format</para>
        /// </summary>
        /// <param name="imageUri">The <see cref="Uri"/> of the source image</param>
        /// <param name="request">The parsed and validated IIIF Image API request</param>
        /// <param name="quality">Image output encoding quality settings</param>
        /// <param name="allowSizeAboveFull">Allow output image dimensions to exceed that of the source image</param>
        /// <param name="pdfMetadata">Optional PDF metadata fields</param>
        /// <returns></returns>
        public async Task <Stream> ProcessImage(Uri imageUri, ImageRequest request, Conf.ImageQuality quality, bool allowSizeAboveFull, Conf.PdfMetadata pdfMetadata)
        {
            var encodingStrategy = GetEncodingStrategy(request.Format);

            if (encodingStrategy == EncodingStrategy.Unknown)
            {
                throw new ArgumentException("Unsupported format", "format");
            }

            var loader = new ImageLoader {
                HttpClient = HttpClient, Log = Log
            };

            (var state, var imageRegion) = await loader.ExtractRegion(imageUri, request, allowSizeAboveFull, quality);

            using (imageRegion)
            {
                var expectedWidth  = state.OutputWidth;
                var expectedHeight = state.OutputHeight;
                var alphaType      = request.Quality == ImageQuality.bitonal ? SKAlphaType.Opaque : SKAlphaType.Premul;

                (var angle, var originX, var originY, var newImgWidth, var newImgHeight) = Rotate(expectedWidth, expectedHeight, request.Rotation.Degrees);

                using (var surface = SKSurface.Create(width: newImgWidth, height: newImgHeight, colorType: SKImageInfo.PlatformColorType, alphaType: alphaType))
                    using (var canvas = surface.Canvas)
                        using (var region = new SKRegion())
                        {
                            // If the rotation parameter includes mirroring ("!"), the mirroring is applied before the rotation.
                            if (request.Rotation.Mirror)
                            {
                                canvas.Translate(newImgWidth, 0);
                                canvas.Scale(-1, 1);
                            }

                            canvas.Translate(originX, originY);
                            canvas.RotateDegrees(angle, 0, 0);

                            // reset clip rects to rotated boundaries
                            region.SetRect(new SKRectI(0 - (int)originX, 0 - (int)originY, newImgWidth, newImgHeight));
                            canvas.ClipRegion(region);

                            // quality
                            if (request.Quality == ImageQuality.gray || request.Quality == ImageQuality.bitonal)
                            {
                                var contrast = request.Quality == ImageQuality.gray ? 0.1f : 1f;
                                using (var cf = SKColorFilter.CreateHighContrast(true, SKHighContrastConfigInvertStyle.NoInvert, contrast))
                                    using (var paint = new SKPaint())
                                    {
                                        paint.FilterQuality = SKFilterQuality.High;
                                        paint.ColorFilter   = cf;

                                        canvas.DrawImage(imageRegion, new SKRect(0, 0, expectedWidth, expectedHeight), paint);
                                    }
                            }
                            else
                            {
                                using (var paint = new SKPaint())
                                {
                                    paint.FilterQuality = SKFilterQuality.High;
                                    canvas.DrawImage(imageRegion, new SKRect(0, 0, expectedWidth, expectedHeight), paint);
                                }
                            }

                            return(Encode(surface,
                                          expectedWidth,
                                          expectedHeight,
                                          encodingStrategy,
                                          request.Format,
                                          quality.GetOutputFormatQuality(request.Format),
                                          pdfMetadata,
                                          state.HorizontalResolution,
                                          state.VerticalResolution));
                        }
            }
        }
Exemple #3
0
        public static (ProcessState state, SKImage image) ExpandRegion(HttpClient client, ILogger Log, Uri imageUri, ImageRequest request, bool allowSizeAboveFull, C.ImageQuality quality)
        {
            using (var compositor = new BitmapCompositor())
                using (var env = new Ckdu_thread_env())
                    using (var family_src = new JPEG2000Source(Log, request.RequestId))
                        using (var wrapped_src = new Cjpx_source())
                            using (var imageDimensions = new Ckdu_dims())
                                using (var limiter = new Ckdu_quality_limiter(quality.WeightedRMSE))
                                {
                                    Ckdu_codestream codestream = new Ckdu_codestream();
                                    try
                                    {
                                        int num_threads = Ckdu_global_funcs.kdu_get_num_processors();
                                        env.create();

                                        for (int nt = 1; nt < num_threads; nt++)
                                        {
                                            if (!env.add_thread())
                                            {
                                                num_threads = nt;
                                            }
                                        }
                                        Log.Debug("Created {@NumThreads} threads", num_threads);
                                        family_src.Open(client, imageUri, false);

                                        int success = wrapped_src.open(family_src, true);
                                        if (success < 1)
                                        {
                                            family_src.close();
                                            wrapped_src.close();
                                            throw new IOException("could not be read as JPEG2000");
                                        }

                                        if (wrapped_src != null)
                                        {
                                            compositor.create(wrapped_src);
                                        }

                                        compositor.set_thread_env(env, null);
                                        compositor.get_total_composition_dims(imageDimensions);
                                        Ckdu_coords imageSize     = imageDimensions.access_size();
                                        Ckdu_coords imagePosition = imageDimensions.access_pos();

                                        float imageScale = 1;

                                        int ref_component = 0;

                                        if (wrapped_src == null)
                                        {
                                            throw new IOException("could not be read as JPEG2000");
                                        }
                                        codestream.create(wrapped_src.access_codestream(ref_component).open_stream());
                                        codestream.set_fast();

                                        int originalWidth, originalHeight;
                                        using (Ckdu_dims original_dims = new Ckdu_dims())
                                        {
                                            codestream.get_dims(ref_component, original_dims);
                                            using (Ckdu_coords original_size = original_dims.access_size())
                                            {
                                                originalWidth  = original_size.x;
                                                originalHeight = original_size.y;
                                            }
                                        }

                                        var kt             = codestream.open_tile(new Ckdu_coords(0, 0), null);
                                        var quality_layers = codestream.get_max_tile_layers();
                                        var layers         = quality.MaxQualityLayers;
                                        if (layers < 0)
                                        {
                                            layers = quality_layers;
                                        }
                                        else if (layers == 0)
                                        {
                                            layers = Convert.ToInt32(Math.Ceiling(quality_layers / 2.0));
                                        }

                                        ushort ppi_x = 96, ppi_y = 96;

                                        if (wrapped_src.access_layer(0).exists())
                                        {
                                            var accessLayer = wrapped_src.access_layer(0);
                                            var resolution  = accessLayer.access_resolution();
                                            if (resolution.exists())
                                            {
                                                bool  for_display     = false;
                                                float ypels_per_metre = resolution.get_resolution(for_display);
                                                if (ypels_per_metre <= 0.0F)
                                                {
                                                    for_display     = true;
                                                    ypels_per_metre = resolution.get_resolution(for_display);
                                                    if (ypels_per_metre <= 0.0F)
                                                    {
                                                        ypels_per_metre = 1.0F;
                                                    }
                                                }

                                                float xpels_per_metre = ypels_per_metre * resolution.get_aspect_ratio(for_display);

                                                ppi_x = Convert.ToUInt16(Math.Ceiling(xpels_per_metre * 0.0254));
                                                ppi_y = Convert.ToUInt16(Math.Ceiling(ypels_per_metre * 0.0254));
                                            }
                                        }

                                        var state = ImageRequestInterpreter.GetInterpretedValues(request, originalWidth, originalHeight, allowSizeAboveFull);
                                        state.HorizontalResolution = ppi_x;
                                        state.VerticalResolution   = ppi_y;
                                        Log.Debug("Image request {@Request}", state);
                                        var scale     = state.OutputScale;
                                        var scaleDiff = 0f;
                                        imageScale = state.ImageScale;

                                        // needs to be able to handle regions
                                        imageSize.x = Convert.ToInt32(Math.Round(state.RegionWidth / scale));
                                        imageSize.y = Convert.ToInt32(Math.Round(state.RegionHeight / scale));

                                        imagePosition.x = state.StartX;
                                        imagePosition.y = state.StartY;

                                        Ckdu_dims extracted_dims = new Ckdu_dims();
                                        extracted_dims.assign(imageDimensions);
                                        extracted_dims.access_size().x = Convert.ToInt32(Math.Round(imageSize.x * imageScale));
                                        extracted_dims.access_size().y = Convert.ToInt32(Math.Round(imageSize.y * imageScale));
                                        var viewSize = extracted_dims.access_size();
                                        Log.Debug("add_ilayer extracted dimension: {@AccessPos}, {@AccessSize}, {@IsEmpty}, {@Scale}", extracted_dims.access_pos(), extracted_dims.access_size(), extracted_dims.is_empty(), scale);
                                        compositor.add_ilayer(0, extracted_dims, extracted_dims);

                                        compositor.set_scale(false, false, false, scale);

                                        compositor.get_total_composition_dims(extracted_dims);
                                        Log.Debug("get_total_composition_dims extracted dimension: {@AccessPos}, {@AccessSize}, {@IsEmpty}, {@Scale}", extracted_dims.access_pos(), extracted_dims.access_size(), extracted_dims.is_empty(), scale);
                                        // check if the access size is the expected size as floating point rounding errors
                                        // might occur
                                        const float roundingValue = 0.0001f;
                                        if (((scale - roundingValue) * imageSize.x > 1 && (scale - roundingValue) * imageSize.y > 1) &&
                                            (scale * imageSize.x != viewSize.x ||
                                             scale * imageSize.y != viewSize.y))
                                        {
                                            // attempt to correct by shifting rounding down
                                            compositor.set_scale(false, false, false, 1, scale - roundingValue);

                                            compositor.get_total_composition_dims(extracted_dims);
                                            extracted_dims.access_size().x = Convert.ToInt32(Math.Round(imageSize.x * imageScale));
                                            extracted_dims.access_size().y = Convert.ToInt32(Math.Round(imageSize.y * imageScale));
                                            viewSize = extracted_dims.access_size();
                                        }

                                        var checkScale = compositor.check_invalid_scale_code();
                                        if (0 != checkScale)
                                        {
                                            // we've come up with a scale factor which is (probably) way too small
                                            // ask Kakadu to come up with a valid one that's close
                                            var minScale = Ckdu_global.KDU_COMPOSITOR_SCALE_TOO_SMALL == checkScale ? scale : 0;
                                            var maxScale = Ckdu_global.KDU_COMPOSITOR_SCALE_TOO_LARGE == checkScale ? scale : 1;

                                            var optimal_scale = compositor.find_optimal_scale(extracted_dims, 0, minScale, maxScale);
                                            scaleDiff = Ckdu_global.KDU_COMPOSITOR_SCALE_TOO_SMALL == checkScale ? optimal_scale - scale : scale - optimal_scale;
                                            scale     = optimal_scale;
                                            compositor.set_scale(false, false, false, scale);
                                            compositor.get_total_composition_dims(extracted_dims);
                                        }
                                        viewSize = extracted_dims.access_size();
                                        Log.Debug("Extracted dimension: {@AccessPos}, {@AccessSize}, {@IsEmpty}, {@Scale}", extracted_dims.access_pos(), extracted_dims.access_size(), extracted_dims.is_empty(), scale);
                                        compositor.set_buffer_surface(extracted_dims);
                                        compositor.set_quality_limiting(limiter, quality.OutputDpi, quality.OutputDpi);
                                        Log.Debug("Set quality limiting: {@Limiter}, {@HorizontalPPI}, {@VerticalPPI}", limiter, quality.OutputDpi, quality.OutputDpi);
                                        compositor.set_max_quality_layers(layers);
                                        Log.Debug("Set max quality layers: {@Layers}", layers);
                                        var compositorBuffer = compositor.GetCompositionBitmap(extracted_dims);

                                        using (Ckdu_dims newRegion = new Ckdu_dims())
                                        {
                                            // we're only interested in the final composited image
                                            while (compositor.process(256000, newRegion))
                                            {
                                            }

                                            using (var bmp = compositorBuffer.AcquireBitmap())
                                            {
                                                return(state, SKImage.FromBitmap(bmp));
                                            }
                                        }
                                    }
                                    finally
                                    {
                                        if (codestream.exists())
                                        {
                                            codestream.destroy();
                                        }

                                        if (env.exists())
                                        {
                                            env.destroy();
                                        }

                                        if (family_src != null)
                                        {
                                            family_src.close();
                                        }
                                        else if (wrapped_src != null)
                                        {
                                            wrapped_src.close();
                                        }
                                    }
                                }
        }