-
-
Notifications
You must be signed in to change notification settings - Fork 106
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement BMP version compareImages_BMP() method for issue#153 #221
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,9 +5,14 @@ | |
import com.github.romankh3.image.comparison.model.ImageComparisonState; | ||
import com.github.romankh3.image.comparison.model.Rectangle; | ||
|
||
import javax.imageio.ImageIO; | ||
import java.awt.*; | ||
import java.awt.image.BufferedImage; | ||
import java.awt.image.MemoryImageSource; | ||
import java.awt.image.PixelGrabber; | ||
import java.io.ByteArrayOutputStream; | ||
import java.io.File; | ||
import java.io.IOException; | ||
import java.util.List; | ||
import java.util.*; | ||
import java.util.stream.Collectors; | ||
|
@@ -185,6 +190,39 @@ public ImageComparison(BufferedImage expected, BufferedImage actual) { | |
} | ||
|
||
/** | ||
* [work for issue #153] | ||
* Draw rectangles which cover the regions of the difference pixels. | ||
* | ||
* @return the result of the drawing. | ||
*/ | ||
public ImageComparisonResult compareImages_BMP() { | ||
|
||
// check that the images have the same size | ||
if (isImageSizesNotEqual(expected, actual)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This logic have code duplicate. Please. provide solution for using it from one place. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think adding a param to compareImages() method to indicate whether using the BMP version could help... |
||
BufferedImage actualResized = ImageComparisonUtil.resize(actual, expected.getWidth(), expected.getHeight()); | ||
return ImageComparisonResult.defaultSizeMisMatchResult(expected, actual, getDifferencePercent(actualResized, expected)); | ||
} | ||
|
||
List<Rectangle> rectangles = populateRectangles_BMP(); | ||
|
||
if (rectangles.isEmpty()) { | ||
ImageComparisonResult matchResult = ImageComparisonResult.defaultMatchResult(expected, actual); | ||
if (drawExcludedRectangles) { | ||
matchResult.setResult(drawRectangles(rectangles)); | ||
saveImageForDestination(matchResult.getResult()); | ||
} | ||
return matchResult; | ||
} | ||
|
||
BufferedImage resultImage = drawRectangles(rectangles); | ||
saveImageForDestination(resultImage); | ||
return ImageComparisonResult.defaultMisMatchResult(expected, actual, getDifferencePercent(actual, expected)) | ||
.setResult(resultImage) | ||
.setRectangles(rectangles); | ||
} | ||
|
||
/** | ||
* [work for issue #153] | ||
* Draw rectangles which cover the regions of the difference pixels. | ||
* | ||
* @return the result of the drawing. | ||
|
@@ -226,6 +264,175 @@ private boolean isImageSizesNotEqual(BufferedImage expected, BufferedImage actua | |
return expected.getHeight() != actual.getHeight() || expected.getWidth() != actual.getWidth(); | ||
} | ||
|
||
/** | ||
* [work for issue #153] | ||
* Turn an image(maybe jpg or png) to BMP format. | ||
* | ||
* @param sourceImg {@link BufferedImage} object of the source image. | ||
* @return a BufferedImage object, the source image in BMP format. | ||
*/ | ||
private static BufferedImage imageToBMP(BufferedImage sourceImg){ | ||
int h = sourceImg.getHeight(); | ||
int w = sourceImg.getWidth(); | ||
int[] pixel = new int[w * h]; | ||
PixelGrabber pixelGrabber = new PixelGrabber(sourceImg, 0, 0, w, h, pixel, 0, w); | ||
try { | ||
pixelGrabber.grabPixels(); | ||
} catch (InterruptedException e) { | ||
e.printStackTrace(); | ||
} | ||
MemoryImageSource m = new MemoryImageSource(w, h, pixel, 0, w); | ||
Image image = Toolkit.getDefaultToolkit().createImage(m); | ||
BufferedImage buff = new BufferedImage(w, h, BufferedImage.TYPE_3BYTE_BGR); | ||
buff.createGraphics().drawImage(image, 0, 0 ,null); | ||
return buff; | ||
} | ||
|
||
/** | ||
* [work for issue #153] | ||
* Turn BufferedImage(BMP) to byte[]. | ||
* | ||
* @param image {@link BufferedImage} object of the source image. | ||
* @return byte[] that stores the information of RGB value of pixels.(and BMP head information) | ||
*/ | ||
private static byte[] bufferedImageToBytes(BufferedImage image) { | ||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); | ||
try { | ||
ImageIO.write(image, "bmp", outputStream); | ||
} catch (IOException e) { | ||
e.printStackTrace(); | ||
} | ||
return outputStream.toByteArray(); | ||
} | ||
|
||
/** | ||
* [work for issue #153] | ||
* a class to store 3 bytes RGB values in a pixel | ||
*/ | ||
private class RGB_tuple{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please, rename it to RgbTuple. |
||
public int red; | ||
public int green; | ||
public int blue; | ||
public RGB_tuple(byte r, byte g, byte b){ | ||
this.red = r; | ||
this.green = g; | ||
this.blue = b; | ||
} | ||
} | ||
|
||
/** | ||
* [work for issue #153] | ||
* Turn BMP byte[] to pixel RGB matrix | ||
* | ||
* @param w width of the matrix | ||
* @param h height of the matrix | ||
* @param bytes byte array which stores the RGB values, 3 bytes in a group | ||
* @return a pixel RGB value tuple matrix | ||
*/ | ||
private RGB_tuple[][] byte2RGBMatrix(int h, int w, byte[] bytes){ | ||
RGB_tuple[][] matrix = new RGB_tuple[h][w]; | ||
int cursor = 54; | ||
for ( int y = h-1; y >= 0; y-- ){ | ||
for ( int x = 0; x < w; x++){ | ||
matrix[y][x] = new RGB_tuple(bytes[cursor+2], bytes[cursor+1], bytes[cursor]); | ||
cursor += 3; | ||
} | ||
} | ||
return matrix; | ||
} | ||
|
||
/** | ||
* [work for issue #153] | ||
* Populate binary matrix with "0" and "1". If the pixels are different set it as "1", otherwise "0". | ||
* In this case, images are turned into BMP format, and the comparison is based on bytes. | ||
* | ||
* @return the count of different pixels | ||
*/ | ||
private long populateTheMatrixOfTheDifferences_BMP() { | ||
// Turn images to BMP format | ||
BufferedImage expectedBMP = imageToBMP(expected); | ||
BufferedImage actualBMP = imageToBMP(actual); | ||
// Turn BMP images to byte[], 2 bytes for 1 pixel | ||
// TYPE_3BYTE_BGR: 1 byte for red, green and blue respectively. | ||
byte[] expectedBytes = bufferedImageToBytes(expectedBMP); | ||
byte[] actualBytes = bufferedImageToBytes(actualBMP); | ||
// Form the RGB byte values as a matrix | ||
int h = expected.getHeight(), w = expected.getWidth(); | ||
RGB_tuple[][] expected_matrix = byte2RGBMatrix(h,w,expectedBytes); | ||
RGB_tuple[][] actual_matrix = byte2RGBMatrix(h,w,actualBytes); | ||
|
||
// count different bytes | ||
long countOfDifferentPixels = 0; | ||
matrix = new int[h][w]; | ||
for (int y = 0; y < h; y++) { | ||
for (int x = 0; x < w; x++) { | ||
if (!excludedAreas.contains(new Point(x, y))) { | ||
if (isDifferentPixels_BMP(expected_matrix[y][x], actual_matrix[y][x])) { | ||
matrix[y][x] = 1; | ||
countOfDifferentPixels++; | ||
} | ||
} | ||
} | ||
} | ||
return countOfDifferentPixels; | ||
} | ||
|
||
/** | ||
* [work for issue #153] | ||
* Say if the two pixels equal or not. The rule is the difference between two pixels | ||
* need to be more than {@link #pixelToleranceLevel}. | ||
* | ||
* @param expectedRgb the RGB value(tuple) of the Pixel of the Expected image. | ||
* @param actualRgb the RGB value(tuple) of the Pixel of the Actual image. | ||
* @return {@code true} if they' are difference, {@code false} otherwise. | ||
*/ | ||
private boolean isDifferentPixels_BMP(RGB_tuple expectedRgb, RGB_tuple actualRgb) { | ||
if (expectedRgb == actualRgb) { | ||
return false; | ||
} else if (pixelToleranceLevel == 0.0) { | ||
return true; | ||
} | ||
|
||
int red1 = expectedRgb.red; | ||
int green1 = expectedRgb.green; | ||
int blue1 = expectedRgb.blue; | ||
int red2 = actualRgb.red; | ||
int green2 = actualRgb.green; | ||
int blue2 = actualRgb.blue; | ||
|
||
return (Math.pow(red2 - red1, 2) + Math.pow(green2 - green1, 2) + Math.pow(blue2 - blue1, 2)) | ||
> differenceConstant; | ||
} | ||
|
||
/** | ||
* [work for issue #153] | ||
* Populate rectangles of the differences | ||
* | ||
* @return the collection of the populated {@link Rectangle} objects. | ||
*/ | ||
private List<Rectangle> populateRectangles_BMP() { | ||
long countOfDifferentPixels = populateTheMatrixOfTheDifferences_BMP(); | ||
|
||
if (countOfDifferentPixels == 0) { | ||
return emptyList(); | ||
} | ||
|
||
if (isAllowedPercentOfDifferentPixels(countOfDifferentPixels)) { | ||
return emptyList(); | ||
} | ||
groupRegions(); | ||
List<Rectangle> rectangles = new ArrayList<>(); | ||
while (counter <= regionCount) { | ||
Rectangle rectangle = createRectangle(); | ||
if (!rectangle.equals(Rectangle.createDefault()) && rectangle.size() >= minimalRectangleSize) { | ||
rectangles.add(rectangle); | ||
} | ||
counter++; | ||
} | ||
|
||
return mergeRectangles(mergeRectangles(rectangles)); | ||
} | ||
|
||
/** | ||
* Populate binary matrix with "0" and "1". If the pixels are different set it as "1", otherwise "0". | ||
* | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
package com.github.romankh3.image.comparison; | ||
|
||
import com.github.romankh3.image.comparison.model.ImageComparisonResult; | ||
import org.junit.jupiter.api.Test; | ||
|
||
import java.awt.image.BufferedImage; | ||
|
||
import static com.github.romankh3.image.comparison.ImageComparisonUtil.readImageFromResources; | ||
import static com.github.romankh3.image.comparison.model.ImageComparisonState.MISMATCH; | ||
import static org.junit.jupiter.api.Assertions.*; | ||
import static org.junit.jupiter.api.Assertions.fail; | ||
|
||
public class BMPTest { | ||
@Test | ||
public void Test1a(){ | ||
BufferedImage result = readImageFromResources("result#21.png"); | ||
BufferedImage expected = readImageFromResources("expected#21.png"); | ||
BufferedImage actual = readImageFromResources("actual#21.png"); | ||
|
||
ImageComparison imageComparison = new ImageComparison(expected,actual); | ||
ImageComparisonResult imageComparisonResult = imageComparison.compareImages(); | ||
|
||
assertNotNull(imageComparison.getActual()); | ||
assertNotNull(imageComparison.getExpected()); | ||
assertEquals(MISMATCH, imageComparisonResult.getImageComparisonState()); | ||
assertImagesEqual(result, imageComparisonResult.getResult()); | ||
} | ||
|
||
@Test | ||
public void Test1b(){ | ||
BufferedImage result = readImageFromResources("result#21.png"); | ||
BufferedImage expected = readImageFromResources("expected#21.png"); | ||
BufferedImage actual = readImageFromResources("actual#21.png"); | ||
|
||
ImageComparison imageComparison = new ImageComparison(expected,actual); | ||
ImageComparisonResult imageComparisonResult = imageComparison.compareImages_BMP(); | ||
|
||
assertNotNull(imageComparison.getActual()); | ||
assertNotNull(imageComparison.getExpected()); | ||
assertEquals(MISMATCH, imageComparisonResult.getImageComparisonState()); | ||
assertImagesEqual(result, imageComparisonResult.getResult()); | ||
} | ||
|
||
@Test | ||
public void Test2a(){ | ||
BufferedImage result = readImageFromResources("test_result_98.png"); | ||
BufferedImage expected = readImageFromResources("expected#98.png"); | ||
BufferedImage actual = readImageFromResources("actual#98.png"); | ||
|
||
ImageComparison imageComparison = new ImageComparison(expected,actual); | ||
ImageComparisonResult imageComparisonResult = imageComparison.compareImages(); | ||
|
||
assertNotNull(imageComparison.getActual()); | ||
assertNotNull(imageComparison.getExpected()); | ||
assertEquals(MISMATCH, imageComparisonResult.getImageComparisonState()); | ||
assertImagesEqual(result, imageComparisonResult.getResult()); | ||
} | ||
|
||
@Test | ||
public void Test2b(){ | ||
BufferedImage result = readImageFromResources("test_result_98.png"); | ||
BufferedImage expected = readImageFromResources("expected#98.png"); | ||
BufferedImage actual = readImageFromResources("actual#98.png"); | ||
|
||
ImageComparison imageComparison = new ImageComparison(expected,actual); | ||
ImageComparisonResult imageComparisonResult = imageComparison.compareImages_BMP(); | ||
|
||
assertNotNull(imageComparison.getActual()); | ||
assertNotNull(imageComparison.getExpected()); | ||
assertEquals(MISMATCH, imageComparisonResult.getImageComparisonState()); | ||
assertImagesEqual(result, imageComparisonResult.getResult()); | ||
} | ||
|
||
private void assertImagesEqual(BufferedImage expected, BufferedImage actual) { | ||
if (expected.getWidth() != actual.getWidth() || expected.getHeight() != actual.getHeight()) { | ||
fail("Images have different dimensions"); | ||
} | ||
|
||
int width = expected.getWidth(); | ||
int height = expected.getHeight(); | ||
|
||
for (int y = 0; y < height; y++) { | ||
for (int x = 0; x < width; x++) { | ||
if (expected.getRGB(x, y) != actual.getRGB(x, y)) { | ||
fail("Images are different, found a different pixel at: x = " + x + ", y = " + y); | ||
} | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please, rename method to be more java friendly.
For example compareImagesBMP or similar.