From eae5243a85ece3003ca3dbe4087c35b2925bbdd3 Mon Sep 17 00:00:00 2001 From: Erik Pragt Date: Mon, 15 Jul 2024 23:54:34 +1000 Subject: [PATCH] Added more customization options for generating different types of images with different dimensions. --- .../net/datafaker/providers/base/Image.java | 166 ++++++++++++++---- .../datafaker/providers/base/ImageTest.java | 96 +++++++++- .../script/RandomImageGenerator.java | 2 +- 3 files changed, 226 insertions(+), 38 deletions(-) diff --git a/src/main/java/net/datafaker/providers/base/Image.java b/src/main/java/net/datafaker/providers/base/Image.java index 5eceda6d5..7360e4229 100644 --- a/src/main/java/net/datafaker/providers/base/Image.java +++ b/src/main/java/net/datafaker/providers/base/Image.java @@ -3,90 +3,177 @@ import javax.imageio.ImageIO; import java.awt.Color; -import java.awt.Graphics2D; +import java.awt.*; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Base64; import static java.awt.Color.WHITE; +import static net.datafaker.providers.base.Image.ImageType.BMP; +import static net.datafaker.providers.base.Image.ImageType.GIF; +import static net.datafaker.providers.base.Image.ImageType.JPEG; +import static net.datafaker.providers.base.Image.ImageType.PNG; +import static net.datafaker.providers.base.Image.ImageType.SVG; +import static net.datafaker.providers.base.Image.ImageType.TIFF; /** - * Generates base64 encoded PNG, GIF, JPG and SVG images. + * Generates base64 encoded raster and vector images. * * @since 2.3.0 */ public class Image extends AbstractProvider { - private static final int WIDTH = 256; - private static final int HEIGHT = 256; - private static final int BOX_SIZE = WIDTH / 8; + private static final int DEFAULT_WIDTH = 256; + private static final int DEFAULT_HEIGHT = 256; + + public enum ImageType { + // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types + BMP("image/bmp"), + GIF("image/gif"), + JPEG("image/jpeg"), + PNG("image/png"), + SVG("image/svg+xml"), + TIFF("image/tiff"); + + private final String mimeType; + + ImageType(String mimeType) { + this.mimeType = mimeType; + } + + public String getMimeType() { + return mimeType; + } + } protected Image(BaseProviders faker) { super(faker); } - public String base64PNG() { - return generateBase64Image("png"); + public String base64BMP() { + return base64(ImageBuilder.builder().type(BMP).build()); + } + + public String base64GIF() { + return base64(ImageBuilder.builder().type(GIF).build()); } public String base64JPG() { - return generateBase64Image("jpeg"); + return base64JPEG(); } - public String base64GIF() { - return generateBase64Image("gif"); + public String base64JPEG() { + return base64(ImageBuilder.builder().type(JPEG).build()); + } + + public String base64PNG() { + return base64(ImageBuilder.builder().type(PNG).build()); } public String base64SVG() { - return generateBase64SVGImage(); + return base64(ImageBuilder.builder().type(SVG).build()); + } + + public String base64TIFF() { + return generateBase64RasterImage(TIFF, DEFAULT_WIDTH, DEFAULT_HEIGHT); + } + + public String base64(Base64ImageRuleConfig config) { + if (config.imageType == SVG) { + return generateBase64VectorImage(config.imageType(), config.width(), config.height()); + } else { + return generateBase64RasterImage(config.imageType(), config.width(), config.height()); + } + } + + public record Base64ImageRuleConfig(ImageType imageType, int width, int height) { } + + public static class ImageBuilder { + private ImageType imageType = PNG; + private int width = DEFAULT_WIDTH; + private int height = DEFAULT_HEIGHT; + + private ImageBuilder() { + } + + public static ImageBuilder builder() { + return new ImageBuilder(); + } + + public Base64ImageRuleConfig build() { + return new Base64ImageRuleConfig(imageType, width, height); + } + + public ImageBuilder type(ImageType imageType) { + if(imageType == null) { + throw new IllegalArgumentException("Type cannot be null"); + } + + this.imageType = imageType; + return this; + } + + public ImageBuilder width(int width) { + if (width <= 0) { + throw new IllegalArgumentException("Width must be greater than 0"); + } + + this.width = width; + return this; + } + + public ImageBuilder height(int height) { + if (height <= 0) { + throw new IllegalArgumentException("Height must be greater than 0"); + } + + this.height = height; + return this; + } } - private String generateBase64Image(String format) { - BufferedImage bufferedImage = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB); + private String generateBase64RasterImage(ImageType imageType, int width, int height) { + BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics2D graphics = bufferedImage.createGraphics(); + int boxSize = Math.max(1, width / 8); + // Fill the image with white background graphics.setColor(WHITE); - graphics.fillRect(0, 0, WIDTH, HEIGHT); + graphics.fillRect(0, 0, width, height); // Draw random colored boxes - for (int y = 0; y < HEIGHT; y += BOX_SIZE) { - for (int x = 0; x < WIDTH; x += BOX_SIZE) { - graphics.setColor(randomColor()); - graphics.fillRect(x, y, BOX_SIZE, BOX_SIZE); + for (int y = 0; y < height; y += boxSize) { + for (int x = 0; x < width; x += boxSize) { + Color randomColor = randomColor(); + graphics.setColor(randomColor); + graphics.fillRect(x, y, boxSize, boxSize); } } graphics.dispose(); try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { - ImageIO.write(bufferedImage, format, baos); + ImageIO.write(bufferedImage, imageType.name(), baos); byte[] imageBytes = baos.toByteArray(); - return "data:image/" + format + ";base64," + Base64.getEncoder().encodeToString(imageBytes); + return "data:" + imageType.mimeType + ";base64," + Base64.getEncoder().encodeToString(imageBytes); } catch (IOException e) { e.printStackTrace(); return null; } } - private Color randomColor() { - // Convert the bytes to unsigned integers (0-255) for RGB - byte[] randomBytes = faker.random().nextRandomBytes(3); - int red = randomBytes[0] & 0xFF; - int green = randomBytes[1] & 0xFF; - int blue = randomBytes[2] & 0xFF; - return new Color(red, green, blue); - } - - private String generateBase64SVGImage() { + private String generateBase64VectorImage(ImageType imageType, int width, int height) { StringBuilder svg = new StringBuilder(); - svg.append(""); + svg.append(""); + + int boxSize = Math.max(1, width / 8); - for (int y = 0; y < HEIGHT; y += BOX_SIZE) { - for (int x = 0; x < WIDTH; x += BOX_SIZE) { + for (int y = 0; y < height; y += boxSize) { + for (int x = 0; x < width; x += boxSize) { Color randomColor = randomColor(); String color = String.format("#%02x%02x%02x", randomColor.getRed(), randomColor.getGreen(), randomColor.getBlue()); - svg.append(""); + svg.append(""); } } @@ -94,6 +181,15 @@ private String generateBase64SVGImage() { String svgString = svg.toString(); String base64Svg = Base64.getEncoder().encodeToString(svgString.getBytes()); - return "data:image/svg+xml;base64," + base64Svg; + return "data:" + imageType.mimeType + ";base64," + base64Svg; + } + + private Color randomColor() { + // Convert the bytes to unsigned integers (0-255) for RGB + byte[] randomBytes = faker.random().nextRandomBytes(3); + int red = randomBytes[0] & 0xFF; + int green = randomBytes[1] & 0xFF; + int blue = randomBytes[2] & 0xFF; + return new Color(red, green, blue); } } diff --git a/src/test/java/net/datafaker/providers/base/ImageTest.java b/src/test/java/net/datafaker/providers/base/ImageTest.java index 859386a13..07deca036 100644 --- a/src/test/java/net/datafaker/providers/base/ImageTest.java +++ b/src/test/java/net/datafaker/providers/base/ImageTest.java @@ -1,11 +1,25 @@ package net.datafaker.providers.base; +import net.datafaker.providers.base.Image.ImageType; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; class ImageTest extends BaseFakerTest { + @Test + void bmp() { + assertThat(faker.image().base64BMP()).startsWith("data:image/bmp;base64,"); + } + + @Test + void gif() { + assertThat(faker.image().base64GIF()).startsWith("data:image/gif;base64,"); + } + @Test void png() { assertThat(faker.image().base64PNG()).startsWith("data:image/png;base64,"); @@ -17,12 +31,90 @@ void jpg() { } @Test - void gif() { - assertThat(faker.image().base64GIF()).startsWith("data:image/gif;base64,"); + void jpeg() { + assertThat(faker.image().base64JPEG()).startsWith("data:image/jpeg;base64,"); } @Test void svg() { assertThat(faker.image().base64SVG()).startsWith("data:image/svg+xml;base64,"); } + + @Test + void tiff() { + assertThat(faker.image().base64TIFF()).startsWith("data:image/tiff;base64,"); + } + + @ParameterizedTest + @EnumSource(ImageType.class) + void base64(ImageType imageType) { + String base64Image = faker.image().base64(new Image.Base64ImageRuleConfig(imageType, 1000, 1000)); + + assertThat(base64Image) + .startsWith("data:" + imageType.getMimeType() + ";base64,"); + assertThat(base64Image.substring(base64Image.indexOf(",") + 1)) + .isNotBlank() + .isBase64(); + } + + @Test + void defaultBuilder() { + String image = faker.image().base64(Image.ImageBuilder.builder() + .build()); + assertThat(image).startsWith("data:image/"); + } + + @Test + void customBase64builder() { + String gif = faker.image().base64(Image.ImageBuilder.builder() + .type(ImageType.GIF) + .build()); + assertThat(gif).startsWith("data:image/gif;base64,"); + } + + @Test + void tinyBase64builder() { + String tiny = faker.image().base64(Image.ImageBuilder.builder() + .height(1) + .width(1) + .type(ImageType.PNG) + .build()); + + assertThat(tiny).startsWith("data:image/png;base64,"); + } + + @Test + void largeBase64builder() { + String large = faker.image().base64(Image.ImageBuilder.builder() + .height(1000) + .width(2000) + .type(ImageType.BMP) + .build()); + assertThat(large).startsWith("data:image/bmp;base64,"); + } + + @Test + void shouldErrorOnIllegalType() { + assertThatIllegalArgumentException().isThrownBy(() -> Image.ImageBuilder.builder().type(null).build()); + } + + @Test + void shouldErrorOnNegativeWidth() { + assertThatIllegalArgumentException().isThrownBy(() -> Image.ImageBuilder.builder().width(-1).build()); + } + + @Test + void shouldErrorOnZeroWidth() { + assertThatIllegalArgumentException().isThrownBy(() -> Image.ImageBuilder.builder().width(0).build()); + } + + @Test + void shouldErrorOnNegativeHeight() { + assertThatIllegalArgumentException().isThrownBy(() -> Image.ImageBuilder.builder().height(-1).build()); + } + + @Test + void shouldErrorOnZeroHeight() { + assertThatIllegalArgumentException().isThrownBy(() -> Image.ImageBuilder.builder().height(0).build()); + } } diff --git a/src/test/java/net/datafaker/script/RandomImageGenerator.java b/src/test/java/net/datafaker/script/RandomImageGenerator.java index 30ae9228b..542fa1030 100644 --- a/src/test/java/net/datafaker/script/RandomImageGenerator.java +++ b/src/test/java/net/datafaker/script/RandomImageGenerator.java @@ -10,7 +10,7 @@ public static void main(String[] args) { System.out.println("PNG Image:"); System.out.println(faker.image().base64PNG()); System.out.println("JPG Image:"); - System.out.println(faker.image().base64JPG()); + System.out.println(faker.image().base64JPEG()); System.out.println("GIF Image:"); System.out.println(faker.image().base64GIF()); System.out.println("SVG Image:");