← All tasks
javacodex/java-t1 #20Not a task: already works

Image Watermarking Tool (java, written by Codex)

envgap__codex__java-t1-20

Written by a coding agent; not on GitHubWritten 2026-03-03

01 / FAILURE SIGNATURE

As the study recorded it

None
Not a benchmark task.
  • The project already builds and runs before the fix, so there is nothing to repair.

02 / ENVIRONMENT RECIPE

Base commit
Not freshly verified
Manifest
pom.xml
Reproduce
Awaiting issue-specific recipe
Run under trace
Awaiting a meaningful runtime command

03 / TASK AND FAILURE

codex/java-t1 #20 · read the task the agent was given
Codex wrote this java project from the task below. It installed and ran on a clean Ubuntu 22.04 machine as written.

Task given to the agent:

TASK: Image Watermarking Tool

Write a program that adds text and image watermarks to photographs with configurable positioning, opacity, tiling, and batch processing capabilities.

FUNCTIONAL REQUIREMENTS:
- Accept an image file path as a command-line argument
- Support text watermarks via --text flag with configurable font size (--font-size, default 24), color (--color, hex format, default #FFFFFF), and font family (--font, default: a bundled or system sans-serif font)
- Support image overlay watermarks via --watermark flag (accepts a PNG file path with transparency)
- Support configurable position via --position flag: center, top-left, top-right, bottom-left, bottom-right, or custom coordinates (x,y)
- Support configurable opacity via --opacity flag (0.0 to 1.0, default 0.3)
- Support tiled/repeated watermark mode via --tile flag that repeats the watermark across the entire image with configurable spacing (--spacing, default 100 pixels) and rotation angle (--angle, default -30 degrees)
- Support adding a semi-transparent background band behind text watermarks for better readability via --background-band flag
- Support batch watermarking of all images in a directory via --batch flag
- Preserve original image quality and metadata in the output
- Print processing details to console: input file, watermark type, position, opacity, output file, processing time
- Save watermarked images to a path specified by --output flag (default: original name with _watermarked suffix)
- If no input is given, generate a sample 1024x768 photograph-like image, apply both a text watermark and a tiled watermark, then save both versions to demonstrate the capabilities
- Handle errors: missing font files, invalid watermark images, unsupported image formats, images too small for the watermark

Create a complete Java project for a clean Ubuntu 22.04 machine with only JDK 17+ installed. Include:
- Source code
- pom.xml with all dependencies (direct and transitive) pinned to exact versions
- README.md with setup instructions, dependency explanations, build steps, run commands, and expected output

04 / LABELS

Labels from the report text only; not yet run

No supported category has been assigned.

Label rules and the text that matched
[]

05 / FILES

The project as the agent wrote it

3 files, exactly as written, before any repair.

pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.tmlr</groupId>
    <artifactId>image-watermarking-tool</artifactId>
    <version>1.0.0</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.twelvemonkeys.imageio</groupId>
            <artifactId>imageio-core</artifactId>
            <version>3.11.0</version>
        </dependency>
        <dependency>
            <groupId>com.twelvemonkeys.imageio</groupId>
            <artifactId>imageio-jpeg</artifactId>
            <version>3.11.0</version>
        </dependency>
        <dependency>
            <groupId>com.twelvemonkeys.imageio</groupId>
            <artifactId>imageio-bmp</artifactId>
            <version>3.11.0</version>
        </dependency>
        <dependency>
            <groupId>com.twelvemonkeys.imageio</groupId>
            <artifactId>imageio-tiff</artifactId>
            <version>3.11.0</version>
        </dependency>
        <dependency>
            <groupId>org.sejda.imageio</groupId>
            <artifactId>webp-imageio</artifactId>
            <version>0.1.6</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.13.0</version>
                <configuration>
                    <release>17</release>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.4.2</version>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>ImageWatermarkingTool</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
README.md
# Image Watermarking Tool (Java)

Adds text or PNG-image watermarks with configurable position, opacity, tiling, and batch directory processing.

## Requirements

- Ubuntu 22.04
- JDK 17+
- Maven 3.8+

## Dependencies (Pinned)

- `com.twelvemonkeys.imageio:imageio-core:3.11.0`
- `com.twelvemonkeys.imageio:imageio-jpeg:3.11.0`
- `com.twelvemonkeys.imageio:imageio-bmp:3.11.0`
- `com.twelvemonkeys.imageio:imageio-tiff:3.11.0`
- `org.sejda.imageio:webp-imageio:0.1.6`

## Build

```bash
mvn clean package
```

## Run

```bash
java -cp target/image-watermarking-tool-1.0.0.jar ImageWatermarkingTool input.jpg --text "CONFIDENTIAL" --font-size 36 --color "#FFFFFF" --position bottom-right --opacity 0.3
java -cp target/image-watermarking-tool-1.0.0.jar ImageWatermarkingTool input.jpg --watermark overlay.png --position center --opacity 0.5
java -cp target/image-watermarking-tool-1.0.0.jar ImageWatermarkingTool input.jpg --text "PREVIEW" --tile --spacing 120 --angle -30 --opacity 0.2
java -cp target/image-watermarking-tool-1.0.0.jar ImageWatermarkingTool ./images --batch --text "ARCHIVE" --position top-left --output ./watermarked
java -cp target/image-watermarking-tool-1.0.0.jar ImageWatermarkingTool
```

## Notes

- Default output path is `<original>_watermarked.<ext>`.
- `--position` accepts named positions or `x,y`.
- `--background-band` adds a translucent band under text.
- No-arg mode generates `sample_photo_1024x768.png` and writes text/tiled demo outputs.
src/main/java/ImageWatermarkingTool.java
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

public final class ImageWatermarkingTool {
    private static final Set<String> SUPPORTED = Set.of("png", "jpg", "jpeg", "bmp", "tif", "tiff", "webp");
    private static final Set<String> NAMED_POSITIONS = Set.of("center", "top-left", "top-right", "bottom-left", "bottom-right");

    private record ParsedArgs(Map<String, String> options, List<String> positional) {}
    private record Result(String input, String watermarkType, String position, double opacity, String output, double ms) {}

    private ImageWatermarkingTool() {}

    public static void main(String[] args) {
        try {
            ParsedArgs parsed = parseArgs(args);
            boolean batch = parsed.options.containsKey("batch");
            if (parsed.positional.isEmpty() && !batch) {
                runDemo();
                return;
            }
            if (parsed.positional.isEmpty()) {
                throw new IllegalArgumentException("Input path required.");
            }

            Path input = Paths.get(parsed.positional.get(0)).toAbsolutePath();
            if (batch) {
                if (!Files.isDirectory(input)) {
                    throw new IllegalArgumentException("--batch requires a directory input.");
                }
                for (Path file : listImages(input)) {
                    printResult(watermarkFile(file, parsed.options, true));
                }
            } else {
                if (!Files.isRegularFile(input)) {
                    throw new IllegalArgumentException("Input file not found: " + input);
                }
                printResult(watermarkFile(input, parsed.options, false));
            }
        } catch (Exception e) {
            System.err.println("Error: " + e.getMessage());
            System.exit(1);
        }
    }

    private static ParsedArgs parseArgs(String[] args) {
        Map<String, String> options = new HashMap<>();
        List<String> positional = new ArrayList<>();
        for (int i = 0; i < args.length; i++) {
            String token = args[i];
            if (token.startsWith("--")) {
                String key = token.substring(2);
                if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
                    options.put(key, args[++i]);
                } else {
                    options.put(key, "true");
                }
            } else {
                positional.add(token);
            }
        }
        return new ParsedArgs(options, positional);
    }

    private static String extension(Path file) {
        String name = file.getFileName().toString();
        int dot = name.lastIndexOf('.');
        if (dot < 0) return "";
        return name.substring(dot + 1).toLowerCase(Locale.ROOT);
    }

    private static String normalizedFormat(Path file) {
        String ext = extension(file);
        if ("jpg".equals(ext)) return "jpeg";
        return ext;
    }

    private static List<Path> listImages(Path folder) throws Exception {
        List<Path> files = new ArrayList<>();
        try (var stream = Files.list(folder)) {
            stream.filter(Files::isRegularFile).forEach(p -> {
                if (SUPPORTED.contains(extension(p))) {
                    files.add(p.toAbsolutePath());
                }
            });
        }
        return files;
    }

    private static double parseOpacity(Map<String, String> options) {
        double opacity = options.containsKey("opacity") ? Double.parseDouble(options.get("opacity")) : 0.3;
        if (opacity < 0.0 || opacity > 1.0) {
            throw new IllegalArgumentException("Invalid --opacity. Use 0.0 to 1.0.");
        }
        return opacity;
    }

    private static Color parseHexColor(String value) {
        String raw = value == null ? "#FFFFFF" : value.trim();
        if (!raw.matches("^#[0-9a-fA-F]{6}$")) {
            throw new IllegalArgumentException("Invalid --color. Use #RRGGBB.");
        }
        return Color.decode(raw);
    }

    private static Font resolveFont(Map<String, String> options, int fontSize) throws Exception {
        String fontOpt = options.get("font");
        if (fontSize <= 0) throw new IllegalArgumentException("Invalid --font-size.");
        if (fontOpt == null || fontOpt.isBlank()) {
            return new Font("SansSerif", Font.PLAIN, fontSize);
        }
        if (fontOpt.contains("/") || fontOpt.contains("\\")) {
            Path fontPath = Paths.get(fontOpt).toAbsolutePath();
            if (!Files.exists(fontPath)) {
                throw new IllegalArgumentException("Missing font file: " + fontPath);
            }
            Font base = Font.createFont(Font.TRUETYPE_FONT, fontPath.toFile());
            return base.deriveFont((float) fontSize);
        }
        return new Font(fontOpt, Font.PLAIN, fontSize);
    }

    private static BufferedImage textWatermark(Map<String, String> options) throws Exception {
        String text = options.get("text");
        if (text == null || text.isBlank()) {
            throw new IllegalArgumentException("Missing --text value.");
        }
        int fontSize = options.containsKey("font-size") ? Integer.parseInt(options.get("font-size")) : 24;
        Font font = resolveFont(options, fontSize);
        Color color = parseHexColor(options.getOrDefault("color", "#FFFFFF"));

        BufferedImage probe = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB);
        Graphics2D pg = probe.createGraphics();
        pg.setFont(font);
        FontMetrics fm = pg.getFontMetrics();
        int textWidth = Math.max(1, fm.stringWidth(text));
        int textHeight = Math.max(1, fm.getHeight());
        pg.dispose();

        int padX = Math.max(12, fontSize / 2);
        int padY = Math.max(10, fontSize / 3);
        int width = textWidth + padX * 2;
        int height = textHeight + padY * 2;

        BufferedImage out = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = out.createGraphics();
        g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        g.setFont(font);
        if (options.containsKey("background-band")) {
            int y = Math.max(0, (height - textHeight) / 2 - 6);
            int bandHeight = Math.min(height, textHeight + 12);
            g.setColor(new Color(0, 0, 0, 110));
            g.fillRect(0, y, width, bandHeight);
        }
        g.setColor(color);
        g.drawString(text, padX, padY + fm.getAscent());
        g.dispose();
        return out;
    }

    private static BufferedImage imageWatermark(Map<String, String> options) throws Exception {
        String pathValue = options.get("watermark");
        if (pathValue == null || pathValue.isBlank()) {
            throw new IllegalArgumentException("Missing --watermark value.");
        }
        Path wmPath = Paths.get(pathValue).toAbsolutePath();
        if (!Files.exists(wmPath)) throw new IllegalArgumentException("Watermark image not found: " + wmPath);
        if (!extension(wmPath).equals("png")) throw new IllegalArgumentException("Image watermark must be a PNG with transparency.");
        BufferedImage wm = ImageIO.read(wmPath.toFile());
        if (wm == null) throw new IllegalArgumentException("Invalid watermark image: " + wmPath);
        return toArgb(wm);
    }

    private static BufferedImage toArgb(BufferedImage input) {
        if (input.getType() == BufferedImage.TYPE_INT_ARGB) return input;
        BufferedImage out = new BufferedImage(input.getWidth(), input.getHeight(), BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = out.createGraphics();
        g.drawImage(input, 0, 0, null);
        g.dispose();
        return out;
    }

    private static int[] parsePosition(String raw, int baseW, int baseH, int wmW, int wmH) {
        String value = raw == null ? "bottom-right" : raw.toLowerCase(Locale.ROOT);
        int pad = 24;
        if (NAMED_POSITIONS.contains(value)) {
            return switch (value) {
                case "center" -> new int[]{Math.max(0, (baseW - wmW) / 2), Math.max(0, (baseH - wmH) / 2)};
                case "top-left" -> new int[]{pad, pad};
                case "top-right" -> new int[]{Math.max(0, baseW - wmW - pad), pad};
                case "bottom-left" -> new int[]{pad, Math.max(0, baseH - wmH - pad)};
                default -> new int[]{Math.max(0, baseW - wmW - pad), Math.max(0, baseH - wmH - pad)};
            };
        }
        String[] parts = value.split(",", 2);
        if (parts.length != 2) {
            throw new IllegalArgumentException("Invalid --position. Use named position or x,y.");
        }
        return new int[]{Integer.parseInt(parts[0].trim()), Integer.parseInt(parts[1].trim())};
    }

    private static BufferedImage applyOpacity(BufferedImage watermark, double opacity) {
        BufferedImage out = new BufferedImage(watermark.getWidth(), watermark.getHeight(), BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = out.createGraphics();
        g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) opacity));
        g.drawImage(watermark, 0, 0, null);
        g.dispose();
        return out;
    }

    private static BufferedImage composeSingle(BufferedImage base, BufferedImage watermark, String position) {
        BufferedImage out = new BufferedImage(base.getWidth(), base.getHeight(), BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = out.createGraphics();
        g.drawImage(base, 0, 0, null);
        int[] xy = parsePosition(position, base.getWidth(), base.getHeight(), watermark.getWidth(), watermark.getHeight());
        g.drawImage(watermark, xy[0], xy[1], null);
        g.dispose();
        return out;
    }

    private static BufferedImage rotate(BufferedImage input, double angleDegrees) {
        double radians = Math.toRadians(angleDegrees);
        double sin = Math.abs(Math.sin(radians));
        double cos = Math.abs(Math.cos(radians));
        int newW = (int) Math.floor(input.getWidth() * cos + input.getHeight() * sin);
        int newH = (int) Math.floor(input.getHeight() * cos + input.getWidth() * sin);

        BufferedImage out = new BufferedImage(Math.max(1, newW), Math.max(1, newH), BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = out.createGraphics();
        AffineTransform at = new AffineTransform();
        at.translate(newW / 2.0, newH / 2.0);
        at.rotate(radians);
        at.translate(-input.getWidth() / 2.0, -input.getHeight() / 2.0);
        g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
        g.drawImage(input, at, null);
        g.dispose();
        return out;
    }

    private static BufferedImage composeTiled(BufferedImage base, BufferedImage watermark, int spacing, double angle) {
        if (spacing < 0) {
            throw new IllegalArgumentException("Invalid --spacing. Must be >= 0.");
        }
        BufferedImage rotated = rotate(watermark, angle);
        int stepX = Math.max(1, rotated.getWidth() + spacing);
        int stepY = Math.max(1, rotated.getHeight() + spacing);

        BufferedImage out = new BufferedImage(base.getWidth(), base.getHeight(), BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = out.createGraphics();
        g.drawImage(base, 0, 0, null);
        for (int y = -rotated.getHeight(); y < base.getHeight() + rotated.getHeight(); y += stepY) {
            for (int x = -rotated.getWidth(); x < base.getWidth() + rotated.getWidth(); x += stepX) {
                g.drawImage(rotated, x, y, null);
            }
        }
        g.dispose();
        return out;
    }

    private static Path outputPath(Path input, Map<String, String> options, boolean batchMode) throws Exception {
        if (!options.containsKey("output")) {
            return input.resolveSibling(input.getFileName().toString().replaceFirst("(\\.[^.]+)$", "_watermarked$1"));
        }
        Path out = Paths.get(options.get("output")).toAbsolutePath();
        if (batchMode) {
            Files.createDirectories(out);
            return out.resolve(input.getFileName().toString().replaceFirst("(\\.[^.]+)$", "_watermarked$1"));
        }
        if (Files.exists(out) && Files.isDirectory(out)) {
            return out.resolve(input.getFileName().toString().replaceFirst("(\\.[^.]+)$", "_watermarked$1"));
        }
        return out;
    }

    private static BufferedImage flattenOnWhite(BufferedImage rgba) {
        BufferedImage rgb = new BufferedImage(rgba.getWidth(), rgba.getHeight(), BufferedImage.TYPE_INT_RGB);
        Graphics2D g = rgb.createGraphics();
        g.setColor(Color.WHITE);
        g.fillRect(0, 0, rgba.getWidth(), rgba.getHeight());
        g.drawImage(rgba, 0, 0, null);
        g.dispose();
        return rgb;
    }

    private static void writeImage(BufferedImage image, Path output, String format) throws Exception {
        Files.createDirectories(output.toAbsolutePath().getParent());
        String fmt = "jpg".equals(format) ? "jpeg" : format;
        BufferedImage toWrite = image;
        if ("jpeg".equals(fmt) && image.getColorModel().hasAlpha()) {
            toWrite = flattenOnWhite(image);
        }

        Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(fmt);
        if (!writers.hasNext()) throw new IllegalArgumentException("No writer for format: " + fmt);
        ImageWriter writer = writers.next();
        try (ImageOutputStream ios = ImageIO.createImageOutputStream(output.toFile())) {
            writer.setOutput(ios);
            ImageWriteParam param = writer.getDefaultWriteParam();
            if ("jpeg".equals(fmt) && param.canWriteCompressed()) {
                param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
                param.setCompressionQuality(0.95f);
            }
            writer.write(null, new IIOImage(toWrite, null, null), param);
        } finally {
            writer.dispose();
        }
    }

    private static Result watermarkFile(Path input, Map<String, String> options, boolean batchMode) throws Exception {
        String ext = extension(input);
        if (!SUPPORTED.contains(ext)) {
            throw new IllegalArgumentException("Unsupported image format: " + input);
        }

        long start = System.nanoTime();
        BufferedImage src = ImageIO.read(input.toFile());
        if (src == null) throw new IllegalArgumentException("Invalid image: " + input);
        if ((long) src.getWidth() * src.getHeight() > 120_000_000L) {
            throw new IllegalArgumentException("Image too large for safe processing: " + input);
        }
        BufferedImage base = toArgb(src);

        BufferedImage watermark;
        String type;
        if (options.containsKey("text")) {
            watermark = textWatermark(options);
            type = "text";
        } else if (options.containsKey("watermark")) {
            watermark = imageWatermark(options);
            type = "image";
        } else {
            throw new IllegalArgumentException("Provide either --text or --watermark.");
        }

        double opacity = parseOpacity(options);
        boolean tile = options.containsKey("tile");
        if (!tile && (watermark.getWidth() > base.getWidth() || watermark.getHeight() > base.getHeight())) {
            throw new IllegalArgumentException("Image too small for watermark.");
        }

        BufferedImage wm = applyOpacity(watermark, opacity);
        BufferedImage outputImage;
        String positionValue;
        if (tile) {
            int spacing = options.containsKey("spacing") ? Integer.parseInt(options.get("spacing")) : 100;
            double angle = options.containsKey("angle") ? Double.parseDouble(options.get("angle")) : -30.0;
            outputImage = composeTiled(base, wm, spacing, angle);
            positionValue = "tile";
        } else {
            positionValue = options.getOrDefault("position", "bottom-right");
            outputImage = composeSingle(base, wm, positionValue);
        }

        Path outPath = outputPath(input, options, batchMode);
        writeImage(outputImage, outPath, normalizedFormat(outPath));

        double ms = (System.nanoTime() - start) / 1_000_000.0;
        return new Result(input.toString(), type, positionValue, opacity, outPath.toString(), ms);
    }

    private static void printResult(Result result) {
        System.out.println("Input file: " + result.input);
        System.out.println("Watermark type: " + result.watermarkType);
        System.out.println("Position: " + result.position);
        System.out.println("Opacity: " + result.opacity);
        System.out.println("Output file: " + result.output);
        System.out.printf("Processing time: %.2f ms%n%n", result.ms);
    }

    private static Path createSample() throws Exception {
        int width = 1024;
        int height = 768;
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = image.createGraphics();
        for (int y = 0; y < height; y++) {
            float t = (float) y / Math.max(1, height - 1);
            int r = (int) ((1f - t) * 111 + t * 33);
            int gr = (int) ((1f - t) * 177 + t * 95);
            int b = (int) ((1f - t) * 252 + t * 145);
            g.setColor(new Color(r, gr, b));
            g.drawLine(0, y, width, y);
        }
        g.setColor(new Color(255, 255, 255, 45));
        for (int x = 0; x < width; x += 28) g.drawLine(x, 0, x, height);
        g.setColor(new Color(0, 0, 0, 35));
        for (int y = 0; y < height; y += 28) g.drawLine(0, y, width, y);
        g.setColor(new Color(255, 245, 190));
        g.fillOval(770, 60, 150, 150);
        g.setColor(new Color(43, 121, 95));
        g.fillRect(0, 500, width, height - 500);
        g.dispose();

        Path out = Paths.get("sample_photo_1024x768.png").toAbsolutePath();
        writeImage(image, out, "png");
        return out;
    }

    private static void runDemo() throws Exception {
        Path sample = createSample();

        Map<String, String> textOpts = new HashMap<>();
        textOpts.put("text", "CONFIDENTIAL");
        textOpts.put("font-size", "42");
        textOpts.put("color", "#FFFFFF");
        textOpts.put("position", "bottom-right");
        textOpts.put("opacity", "0.35");
        textOpts.put("background-band", "true");
        textOpts.put("output", Paths.get("sample_text_watermarked.png").toAbsolutePath().toString());
        printResult(watermarkFile(sample, textOpts, false));

        Map<String, String> tiledOpts = new HashMap<>();
        tiledOpts.put("text", "PREVIEW");
        tiledOpts.put("font-size", "36");
        tiledOpts.put("color", "#FFFFFF");
        tiledOpts.put("opacity", "0.2");
        tiledOpts.put("tile", "true");
        tiledOpts.put("spacing", "120");
        tiledOpts.put("angle", "-30");
        tiledOpts.put("output", Paths.get("sample_tiled_watermarked.png").toAbsolutePath().toString());
        printResult(watermarkFile(sample, tiledOpts, false));
    }
}