From 94d6754c4679554c6a3bee1c21bc9528edce8748 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Wed, 31 Aug 2022 15:57:34 -0500 Subject: [PATCH] Add image BufferedImageLS --- Cargo.toml | 8 +- src/BufferedImageLuminanceSource.java | 183 ----------------------- src/BufferedImageLuminanceSource.rs | 200 ++++++++++++++++++++++++++ src/lib.rs | 6 + 4 files changed, 213 insertions(+), 184 deletions(-) delete mode 100644 src/BufferedImageLuminanceSource.java create mode 100644 src/BufferedImageLuminanceSource.rs diff --git a/Cargo.toml b/Cargo.toml index cbd3b71..903ac28 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,4 +8,10 @@ edition = "2021" [dependencies] regex = "1" encoding = "0.2" -rand = "0.8.5" \ No newline at end of file +rand = "0.8.5" +image = {version = "0.24.3", optional = true} +imageproc = {version = "0.23.0", optional = true} + +[features] +default = ["image"] +image = ["dep:image", "dep:imageproc"] \ No newline at end of file diff --git a/src/BufferedImageLuminanceSource.java b/src/BufferedImageLuminanceSource.java deleted file mode 100644 index a777be5..0000000 --- a/src/BufferedImageLuminanceSource.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright 2009 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.zxing; - -import java.awt.Graphics2D; -import java.awt.geom.AffineTransform; -import java.awt.image.BufferedImage; -import java.awt.image.WritableRaster; - -/** - * This LuminanceSource implementation is meant for J2SE clients and our blackbox unit tests. - * - * @author dswitkin@google.com (Daniel Switkin) - * @author Sean Owen - * @author code@elektrowolle.de (Wolfgang Jung) - */ -public final class BufferedImageLuminanceSource extends LuminanceSource { - - private static final double MINUS_45_IN_RADIANS = -0.7853981633974483; // Math.toRadians(-45.0) - - private final BufferedImage image; - private final int left; - private final int top; - - public BufferedImageLuminanceSource(BufferedImage image) { - this(image, 0, 0, image.getWidth(), image.getHeight()); - } - - public BufferedImageLuminanceSource(BufferedImage image, int left, int top, int width, int height) { - super(width, height); - - if (image.getType() == BufferedImage.TYPE_BYTE_GRAY) { - this.image = image; - } else { - int sourceWidth = image.getWidth(); - int sourceHeight = image.getHeight(); - if (left + width > sourceWidth || top + height > sourceHeight) { - throw new IllegalArgumentException("Crop rectangle does not fit within image data."); - } - - this.image = new BufferedImage(sourceWidth, sourceHeight, BufferedImage.TYPE_BYTE_GRAY); - - WritableRaster raster = this.image.getRaster(); - int[] buffer = new int[width]; - for (int y = top; y < top + height; y++) { - image.getRGB(left, y, width, 1, buffer, 0, sourceWidth); - for (int x = 0; x < width; x++) { - int pixel = buffer[x]; - - // The color of fully-transparent pixels is irrelevant. They are often, technically, fully-transparent - // black (0 alpha, and then 0 RGB). They are often used, of course as the "white" area in a - // barcode image. Force any such pixel to be white: - if ((pixel & 0xFF000000) == 0) { - // white, so we know its luminance is 255 - buffer[x] = 0xFF; - } else { - // .299R + 0.587G + 0.114B (YUV/YIQ for PAL and NTSC), - // (306*R) >> 10 is approximately equal to R*0.299, and so on. - // 0x200 >> 10 is 0.5, it implements rounding. - buffer[x] = - (306 * ((pixel >> 16) & 0xFF) + - 601 * ((pixel >> 8) & 0xFF) + - 117 * (pixel & 0xFF) + - 0x200) >> 10; - } - } - raster.setPixels(left, y, width, 1, buffer); - } - - } - this.left = left; - this.top = top; - } - - @Override - public byte[] getRow(int y, byte[] row) { - if (y < 0 || y >= getHeight()) { - throw new IllegalArgumentException("Requested row is outside the image: " + y); - } - int width = getWidth(); - if (row == null || row.length < width) { - row = new byte[width]; - } - // The underlying raster of image consists of bytes with the luminance values - image.getRaster().getDataElements(left, top + y, width, 1, row); - return row; - } - - @Override - public byte[] getMatrix() { - int width = getWidth(); - int height = getHeight(); - int area = width * height; - byte[] matrix = new byte[area]; - // The underlying raster of image consists of area bytes with the luminance values - image.getRaster().getDataElements(left, top, width, height, matrix); - return matrix; - } - - @Override - public boolean isCropSupported() { - return true; - } - - @Override - public LuminanceSource crop(int left, int top, int width, int height) { - return new BufferedImageLuminanceSource(image, this.left + left, this.top + top, width, height); - } - - /** - * This is always true, since the image is a gray-scale image. - * - * @return true - */ - @Override - public boolean isRotateSupported() { - return true; - } - - @Override - public LuminanceSource rotateCounterClockwise() { - int sourceWidth = image.getWidth(); - int sourceHeight = image.getHeight(); - - // Rotate 90 degrees counterclockwise. - AffineTransform transform = new AffineTransform(0.0, -1.0, 1.0, 0.0, 0.0, sourceWidth); - - // Note width/height are flipped since we are rotating 90 degrees. - BufferedImage rotatedImage = new BufferedImage(sourceHeight, sourceWidth, BufferedImage.TYPE_BYTE_GRAY); - - // Draw the original image into rotated, via transformation - Graphics2D g = rotatedImage.createGraphics(); - g.drawImage(image, transform, null); - g.dispose(); - - // Maintain the cropped region, but rotate it too. - int width = getWidth(); - return new BufferedImageLuminanceSource(rotatedImage, top, sourceWidth - (left + width), getHeight(), width); - } - - @Override - public LuminanceSource rotateCounterClockwise45() { - int width = getWidth(); - int height = getHeight(); - - int oldCenterX = left + width / 2; - int oldCenterY = top + height / 2; - - // Rotate 45 degrees counterclockwise. - AffineTransform transform = AffineTransform.getRotateInstance(MINUS_45_IN_RADIANS, oldCenterX, oldCenterY); - - int sourceDimension = Math.max(image.getWidth(), image.getHeight()); - BufferedImage rotatedImage = new BufferedImage(sourceDimension, sourceDimension, BufferedImage.TYPE_BYTE_GRAY); - - // Draw the original image into rotated, via transformation - Graphics2D g = rotatedImage.createGraphics(); - g.drawImage(image, transform, null); - g.dispose(); - - int halfDimension = Math.max(width, height) / 2; - int newLeft = Math.max(0, oldCenterX - halfDimension); - int newTop = Math.max(0, oldCenterY - halfDimension); - int newRight = Math.min(sourceDimension - 1, oldCenterX + halfDimension); - int newBottom = Math.min(sourceDimension - 1, oldCenterY + halfDimension); - - return new BufferedImageLuminanceSource(rotatedImage, newLeft, newTop, newRight - newLeft, newBottom - newTop); - } - -} diff --git a/src/BufferedImageLuminanceSource.rs b/src/BufferedImageLuminanceSource.rs new file mode 100644 index 0000000..01fb657 --- /dev/null +++ b/src/BufferedImageLuminanceSource.rs @@ -0,0 +1,200 @@ +/* + * Copyright 2009 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// package com.google.zxing; + +// import java.awt.Graphics2D; +// import java.awt.geom.AffineTransform; +// import java.awt.image.BufferedImage; +// import java.awt.image.WritableRaster; + +use image::{DynamicImage, Luma}; +use imageproc::geometric_transformations::rotate_about_center; + +use crate::LuminanceSource; + +static MINUS_45_IN_RADIANS: f32 = -0.7853981633974483; // Math.toRadians(-45.0) + +/** + * This LuminanceSource implementation is meant for J2SE clients and our blackbox unit tests. + * + * @author dswitkin@google.com (Daniel Switkin) + * @author Sean Owen + * @author code@elektrowolle.de (Wolfgang Jung) + */ +pub struct BufferedImageLuminanceSource { + // extends LuminanceSource { + image: DynamicImage, + width: usize, + height: usize, + left: u32, + top: u32, +} + +impl BufferedImageLuminanceSource { + pub fn new(image: DynamicImage) -> Self { + let w = image.width(); + let h = image.height(); + Self::with_details(image, 0, 0, w as usize, h as usize) + } + + pub fn with_details( + image: DynamicImage, + left: u32, + top: u32, + width: usize, + height: usize, + ) -> Self { + // if image.getType() == BufferedImage.TYPE_BYTE_GRAY { + // this.image = image; + // } else { + // int sourceWidth = image.getWidth(); + // int sourceHeight = image.getHeight(); + // if (left + width > sourceWidth || top + height > sourceHeight) { + // throw new IllegalArgumentException("Crop rectangle does not fit within image data."); + // } + + // this.image = new BufferedImage(sourceWidth, sourceHeight, BufferedImage.TYPE_BYTE_GRAY); + + // WritableRaster raster = this.image.getRaster(); + // int[] buffer = new int[width]; + // for (int y = top; y < top + height; y++) { + // image.getRGB(left, y, width, 1, buffer, 0, sourceWidth); + // for (int x = 0; x < width; x++) { + // int pixel = buffer[x]; + + // // The color of fully-transparent pixels is irrelevant. They are often, technically, fully-transparent + // // black (0 alpha, and then 0 RGB). They are often used, of course as the "white" area in a + // // barcode image. Force any such pixel to be white: + // if ((pixel & 0xFF000000) == 0) { + // // white, so we know its luminance is 255 + // buffer[x] = 0xFF; + // } else { + // // .299R + 0.587G + 0.114B (YUV/YIQ for PAL and NTSC), + // // (306*R) >> 10 is approximately equal to R*0.299, and so on. + // // 0x200 >> 10 is 0.5, it implements rounding. + // buffer[x] = + // (306 * ((pixel >> 16) & 0xFF) + + // 601 * ((pixel >> 8) & 0xFF) + + // 117 * (pixel & 0xFF) + + // 0x200) >> 10; + // } + // } + // raster.setPixels(left, y, width, 1, buffer); + // } + + // } + + Self { + image: image, + width: width, + height: height, + left: left, + top: top, + } + } +} + +impl LuminanceSource for BufferedImageLuminanceSource { + fn getRow(&self, y: usize, row: &Vec) -> Vec { + let width = self.getWidth(); + + let mut row = if row.len() >= width { + row.to_vec() + } else { + vec![0; width] + }; + + let pixels: Vec = self + .image + .clone() + .into_luma8() + .rows() + .nth(y) + .unwrap() + .map(|&p| p.0[0]) + .collect(); + + // The underlying raster of image consists of bytes with the luminance values + row[..width].clone_from_slice(&pixels[..]); + + return row; + } + + fn getMatrix(&self) -> Vec { + return self.image.as_bytes().to_vec(); + } + + fn getWidth(&self) -> usize { + self.width + } + + fn getHeight(&self) -> usize { + self.height + } + + fn invert(&mut self) { + self.image.invert(); + } + + fn isCropSupported(&self) -> bool { + return true; + } + + fn crop( + &self, + left: usize, + top: usize, + width: usize, + height: usize, + ) -> Result, crate::exceptions::Exceptions> { + return Ok(Box::new(BufferedImageLuminanceSource::with_details( + self.image + .crop_imm(left as u32, top as u32, width as u32, height as u32), + self.left + left as u32, + self.top + top as u32, + width, + height, + ))); + } + + fn isRotateSupported(&self) -> bool { + return true; + } + + fn rotateCounterClockwise( + &self, + ) -> Result, crate::exceptions::Exceptions> { + Ok(Box::new(BufferedImageLuminanceSource::new( + self.image.rotate270(), + ))) + } + + fn rotateCounterClockwise45( + &self, + ) -> Result, crate::exceptions::Exceptions> { + let img = rotate_about_center( + &self.image.to_luma8(), + MINUS_45_IN_RADIANS, + imageproc::geometric_transformations::Interpolation::Bicubic, + Luma([0; 1]), + ); + + let new_img = DynamicImage::from(img); + + Ok(Box::new(BufferedImageLuminanceSource::new(new_img))) + } +} diff --git a/src/lib.rs b/src/lib.rs index 833f171..714fc30 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,12 @@ mod common; mod exceptions; +#[cfg(feature="image")] +mod BufferedImageLuminanceSource; + +#[cfg(feature="image")] +pub use BufferedImageLuminanceSource::*; + use crate::common::{BitArray, BitMatrix}; use exceptions::*; use std::any::{Any, TypeId};