All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.pepsoft.worldpainter.brushes.BitmapBrush Maven / Gradle / Ivy

There is a newer version: 2.23.2
Show newest version
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.pepsoft.worldpainter.brushes;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.awt.image.RescaleOp;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;

/**
 * A {@link Brush} based on a bitmap image. If it is constructed with a file to load the image from, then the image is
 * stored as a weak reference so that it can be garbage collected to make space on the heap; it will be reloaded as
 * needed from the original file.
 *
 * @author pepijn
 */
public final class BitmapBrush extends AbstractBrush {
    public BitmapBrush(InputStream in, String name) {
        super(name);
        imageFile = null;
        image = loadImage(in);
        imageReference = null;
    }
    
    public BitmapBrush(File imageFile) {
        super(imageFile.getName());
        if (! imageFile.isFile()) {
            throw new IllegalArgumentException(imageFile + " is not a file or does not exist");
        } else if (! imageFile.canRead()) {
            throw new IllegalArgumentException(imageFile + " is not readable");
        }
        this.imageFile = imageFile;
        image = null;
    }

    @Override
    public float getStrength(int dx, int dy) {
        return getData().buffer.getElem(0, dx + radius + (dy + radius) * diameter) / 255f;
    }

    @Override
    public float getFullStrength(int dx, int dy) {
        return getData().fullStrengthBuffer.getElem(0, dx + radius + (dy + radius) * diameter) / 255f;
    }
    
    @Override
    public int getRadius() {
        return radius;
    }

    @Override
    public void setRadius(int radius) {
        if (radius != this.radius) {
            logger.debug("{{}} radius going from {} to {}", getName(), this.radius, radius);
            this.radius = radius;
            diameter = radius * 2 + 1;
            dataReference = null;
        }
    }

    @Override
    public float getLevel() {
        return level;
    }

    @Override
    public void setLevel(float level) {
        if (level != this.level) {
            logger.debug("{{}} level going from {} to {}", getName(), this.level, level);
            this.level = level;
            rescaleOp = new RescaleOp(level, 0, null);
            dataReference = null;
        }
    }

    @Override
    public BrushShape getBrushShape() {
        return BrushShape.BITMAP;
    }

    private Data getData() {
        Data data = (dataReference != null) ? dataReference.get() : null;
        if (data == null) {
            logger.debug("{{}} data not present; (re)creating data", getName());
            data = createData();
            dataReference = new SoftReference<>(data);
        }
        return data;
    }

    private Data createData() {
        BufferedImage image = this.image;
        if (image == null) {
            image = (imageReference != null) ? imageReference.get() : null;
            if (image == null) {
                logger.debug("{{}} image not present; (re)loading image {}", getName(), imageFile);
                image = loadImage(imageFile);
                imageReference = new SoftReference<>(image);
            }
        }
        BufferedImage fullStrengthMask = new BufferedImage(diameter, diameter, BufferedImage.TYPE_BYTE_GRAY);
        Graphics2D g2 = fullStrengthMask.createGraphics();
        try {
            g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
            g2.drawImage(image, 0, 0, diameter, diameter, null);
        } finally {
            g2.dispose();
        }
        Data data = new Data();
        data.fullStrengthBuffer = (DataBufferByte) fullStrengthMask.getRaster().getDataBuffer();
        if (level < 1.0f) {
            BufferedImage mask = rescaleOp.filter(fullStrengthMask, null);
            data.buffer = (DataBufferByte) mask.getRaster().getDataBuffer();
        } else {
            data.buffer = data.fullStrengthBuffer;
        }
        return data;
    }

    private BufferedImage loadImage(File imageFile) {
        try {
            return ImageIO.read(imageFile);
        } catch (IOException e) {
            throw new RuntimeException("I/O error reading image file " + imageFile, e);
        }
    }
    
    private BufferedImage loadImage(InputStream in) {
        try {
            return ImageIO.read(in);
        } catch (IOException e) {
            throw new RuntimeException("I/O error reading image from classpath", e);
        }
    }

    private final File imageFile;
    private final BufferedImage image; // Used when the image is loaded from a stream and cannot be recreated
    private Reference imageReference; // Used when the image is loaded from a file and can be reloaded
    private Reference dataReference;
    private int radius, diameter = 1;
    private float level = 1.0f;
    private RescaleOp rescaleOp = new RescaleOp(1.0f, 0, null);

    private static final Logger logger = LoggerFactory.getLogger(BitmapBrush.class);

    static class Data {
        DataBufferByte fullStrengthBuffer, buffer;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy