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)); } } }
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(); } } } }