<commit_before>//
// $Id: ImageUtil.java,v 1.27 2003/01/21 22:11:04 mdb Exp $

package com.threerings.media.image;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Transparency;

import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.IndexColorModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;

import java.awt.geom.Area;

import java.util.Arrays;
import java.util.Iterator;

import com.samskivert.util.ArrayUtil;
import com.samskivert.util.StringUtil;

import com.threerings.media.Log;

/**
 * Image related utility functions.
 */
public class ImageUtil
{
    /**
     * Creates a new buffered image with the same sample model and color
     * model as the source image but with the new width and height.
     */
    public static BufferedImage createCompatibleImage (
        BufferedImage source, int width, int height)
    {
        WritableRaster raster =
            source.getRaster().createCompatibleWritableRaster(width, height);
        return new BufferedImage(source.getColorModel(), raster, false, null);
    }

    /**
     * Used to recolor images by shifting bands of color (in HSV color
     * space) to a new hue. The source images must be 8-bit color mapped
     * images, as the recoloring process works by analysing the color map
     * and modifying it.
     */
    public static BufferedImage recolorImage (
        BufferedImage image, Color rootColor, float[] dists, float[] offsets)
    {
        return recolorImage(image, new Colorization[] {
            new Colorization(-1, rootColor, dists, offsets) });
    }

    /**
     * Recolors the supplied image as in {@link
     * #recolorImage(BufferedImage,Color,float[],float[])} obtaining the
     * recoloring parameters from the supplied {@link Colorization}
     * instance.
     */
    public static BufferedImage recolorImage (
        BufferedImage image, Colorization cz)
    {
        return recolorImage(image, new Colorization[] { cz });
    }

    /**
     * Recolors the supplied image using the supplied colorizations.
     */
    public static BufferedImage recolorImage (
        BufferedImage image, Colorization[] zations)
    {
        ColorModel cm = image.getColorModel();
        if (!(cm instanceof IndexColorModel)) {
            String errmsg = "Unable to recolor images with non-index color " +
                "model [cm=" + cm.getClass() + "]";
            throw new RuntimeException(errmsg);
        }

        // now process the image
        IndexColorModel icm = (IndexColorModel)cm;
        int size = icm.getMapSize();
        int zcount = zations.length;
        int[] rgbs = new int[size];

        // fetch the color data
        icm.getRGBs(rgbs);

        // convert the colors to HSV
        float[] hsv = new float[3];
        int[] fhsv = new int[3];
        int tpixel = -1;
        for (int i = 0; i < size; i++) {
            int value = rgbs[i];

            // don't fiddle with alpha pixels
            if ((value & 0xFF000000) == 0) {
                tpixel = i;
                continue;
            }

            // convert the color to HSV
            int red = (value >> 16) & 0xFF;
            int green = (value >> 8) & 0xFF;
            int blue = (value >> 0) & 0xFF;
            Color.RGBtoHSB(red, green, blue, hsv);
            Colorization.toFixedHSV(hsv, fhsv);

            // see if this color matches and of our colorizations and
            // recolor it if it does
            for (int z = 0; z < zcount; z++) {
                Colorization cz = zations[z];
                if (cz != null && cz.matches(hsv, fhsv)) {
                    // massage the HSV bands and update the RGBs array
                    rgbs[i] = cz.recolorColor(hsv);
                    break;
                }
            }
        }

        // create a new image with the adjusted color palette
        IndexColorModel nicm = new IndexColorModel(
            icm.getPixelSize(), size, rgbs, 0, icm.hasAlpha(),
            icm.getTransparentPixel(), icm.getTransferType());
        return new BufferedImage(nicm, image.getRaster(), false, null);
    }

    /**
     * Paints multiple copies of the supplied image using the supplied
     * graphics context such that the requested width is filled with the
     * image.
     */
    public static void tileImageAcross (Graphics g, Image image,
                                        int x, int y, int width)
    {
        int iwidth = image.getWidth(null), iheight = image.getHeight(null);
        int tcount = width/iwidth, extra = width % iwidth;

        // draw the full copies of the image
        for (int ii = 0; ii < tcount; ii++) {
            g.drawImage(image, x, y, null);
            x += iwidth;
        }

        // clip the final blit
        if (extra > 0) {
            Shape oclip = g.getClip();
            g.clipRect(x, y, extra, iheight);
            g.drawImage(image, x, y, null);
            g.setClip(oclip);
        }
    }

    /**
     * Paints multiple copies of the supplied image using the supplied
     * graphics context such that the requested height is filled with the
     * image.
     */
    public static void tileImageDown (Graphics g, Image image,
                                      int x, int y, int height)
    {
        int iwidth = image.getWidth(null), iheight = image.getHeight(null);
        int tcount = height/iheight, extra = height % iheight;

        // draw the full copies of the image
        for (int ii = 0; ii < tcount; ii++) {
            g.drawImage(image, x, y, null);
            y += iheight;
        }

        // clip the final blit
        if (extra > 0) {
            Shape oclip = g.getClip();
            g.clipRect(x, y, iwidth, extra);
            g.drawImage(image, x, y, null);
            g.setClip(oclip);
        }
    }

    // Not fully added because we're not using it anywhere, plus
    // it's probably a little sketchy to create Area objects with all
    // this pixely data.
    // Also, the Area was getting zeroed out when it was translated. Something
    // to look into someday if anyone wants to use this method.
//    /**
//     * Creates a mask that is opaque in the non-transparent areas of the
//     * source image.
//     */
//    public static Area createImageMask (BufferedImage src)
//    {
//        Raster srcdata = src.getData(); 
//        if (srcdata.getNumBands() != 4) {
//            throw new IllegalArgumentException(
//                "Can't create a mask of an image with no transparency " +
//                "[image=" + src + "].");
//        }
//
//        int wid = src.getWidth(), hei = src.getHeight();
//        Log.info("creating area of (" + wid + ", " + hei + ")");
//        Area a = new Area(new Rectangle(wid, hei));
//        Rectangle r = new Rectangle(1, 1);
//
//        for (int yy=0; yy < hei; yy++) {
//            for (int xx=0; xx < wid; xx++) {
//                if (srcdata.getSample(xx, yy, 0) == 0) {
//                    r.setLocation(xx, yy);
//                    a.subtract(new Area(r));
//                }
//            }
//        }
//
//        return a;
//    }

    /**
     * Creates and returns a new image consisting of the supplied image
     * traced with the given color and thickness.
     */
    public static BufferedImage createTracedImage (
        ImageManager imgr, BufferedImage src, Color tcolor, int thickness)
    {
        return createTracedImage(imgr, src, tcolor, thickness, 1.0f, 1.0f);
    }

    /**
     * Creates and returns a new image consisting of the supplied image
     * traced with the given color, thickness and alpha transparency.
     */
    public static BufferedImage createTracedImage (
        ImageManager imgr, BufferedImage src, Color tcolor, int thickness,
        float startAlpha, float endAlpha)
    {
        Raster srcdata = src.getData(); 
        if (srcdata.getNumBands() != 4) {
            throw new IllegalArgumentException(
                "Can't trace an image with no transparency " +
                "[image=" + src + ", bands=" + srcdata.getNumBands() + "].");
        }

        // create the destination image
        int wid = src.getWidth(), hei = src.getHeight();
        BufferedImage dest = imgr.createImage(
            wid, hei, Transparency.TRANSLUCENT);

        // prepare various bits of working data
        int srcTrans = src.getColorModel().getTransparency();
        int[] tpixel = new int[] {<commit_msg>Added createErrorImage(); modified createTracedImage() and createImageMask() not to freak out if the source image is not RGBA, Java automatically unpacks pixels appropriately for 8-bit colormapped images, so there's no need to be so picky. In createTracedImage() we were converting the image to a screen optimized image anyway and thus the source image format had absolutely no bearing on what we did with the image later.<commit_after>//
// $Id: ImageUtil.java,v 1.28 2003/01/24 21:48:14 mdb Exp $

package com.threerings.media.image;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Transparency;

import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.IndexColorModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;

import java.awt.geom.Area;

import java.util.Arrays;
import java.util.Iterator;

import com.samskivert.swing.Label;
import com.samskivert.util.ArrayUtil;
import com.samskivert.util.StringUtil;

import com.threerings.media.Log;

/**
 * Image related utility functions.
 */
public class ImageUtil
{
    /**
     * Creates a new buffered image with the same sample model and color
     * model as the source image but with the new width and height.
     */
    public static BufferedImage createCompatibleImage (
        BufferedImage source, int width, int height)
    {
        WritableRaster raster =
            source.getRaster().createCompatibleWritableRaster(width, height);
        return new BufferedImage(source.getColorModel(), raster, false, null);
    }

    /**
     * Creates an image with the word "Error" written in it.
     */
    public static BufferedImage createErrorImage (int width, int height)
    {
        BufferedImage img = new BufferedImage(
            width, height, BufferedImage.TYPE_BYTE_INDEXED);
        Graphics2D g = (Graphics2D)img.getGraphics();
        g.setColor(Color.red);
        Label l = new Label("Error");
        l.layout(g);
        Dimension d = l.getSize();
        // fill that sucker with errors
        for (int yy = 0; yy < height; yy += d.height) {
            for (int xx = 0; xx < width; xx += (d.width+5)) {
                l.render(g, xx, yy);
            }
        }
        g.dispose();
        return img;
    }

    /**
     * Used to recolor images by shifting bands of color (in HSV color
     * space) to a new hue. The source images must be 8-bit color mapped
     * images, as the recoloring process works by analysing the color map
     * and modifying it.
     */
    public static BufferedImage recolorImage (
        BufferedImage image, Color rootColor, float[] dists, float[] offsets)
    {
        return recolorImage(image, new Colorization[] {
            new Colorization(-1, rootColor, dists, offsets) });
    }

    /**
     * Recolors the supplied image as in {@link
     * #recolorImage(BufferedImage,Color,float[],float[])} obtaining the
     * recoloring parameters from the supplied {@link Colorization}
     * instance.
     */
    public static BufferedImage recolorImage (
        BufferedImage image, Colorization cz)
    {
        return recolorImage(image, new Colorization[] { cz });
    }

    /**
     * Recolors the supplied image using the supplied colorizations.
     */
    public static BufferedImage recolorImage (
        BufferedImage image, Colorization[] zations)
    {
        ColorModel cm = image.getColorModel();
        if (!(cm instanceof IndexColorModel)) {
            String errmsg = "Unable to recolor images with non-index color " +
                "model [cm=" + cm.getClass() + "]";
            throw new RuntimeException(errmsg);
        }

        // now process the image
        IndexColorModel icm = (IndexColorModel)cm;
        int size = icm.getMapSize();
        int zcount = zations.length;
        int[] rgbs = new int[size];

        // fetch the color data
        icm.getRGBs(rgbs);

        // convert the colors to HSV
        float[] hsv = new float[3];
        int[] fhsv = new int[3];
        int tpixel = -1;
        for (int i = 0; i < size; i++) {
            int value = rgbs[i];

            // don't fiddle with alpha pixels
            if ((value & 0xFF000000) == 0) {
                tpixel = i;
                continue;
            }

            // convert the color to HSV
            int red = (value >> 16) & 0xFF;
            int green = (value >> 8) & 0xFF;
            int blue = (value >> 0) & 0xFF;
            Color.RGBtoHSB(red, green, blue, hsv);
            Colorization.toFixedHSV(hsv, fhsv);

            // see if this color matches and of our colorizations and
            // recolor it if it does
            for (int z = 0; z < zcount; z++) {
                Colorization cz = zations[z];
                if (cz != null && cz.matches(hsv, fhsv)) {
                    // massage the HSV bands and update the RGBs array
                    rgbs[i] = cz.recolorColor(hsv);
                    break;
                }
            }
        }

        // create a new image with the adjusted color palette
        IndexColorModel nicm = new IndexColorModel(
            icm.getPixelSize(), size, rgbs, 0, icm.hasAlpha(),
            icm.getTransparentPixel(), icm.getTransferType());
        return new BufferedImage(nicm, image.getRaster(), false, null);
    }

    /**
     * Paints multiple copies of the supplied image using the supplied
     * graphics context such that the requested width is filled with the
     * image.
     */
    public static void tileImageAcross (Graphics g, Image image,
                                        int x, int y, int width)
    {
        int iwidth = image.getWidth(null), iheight = image.getHeight(null);
        int tcount = width/iwidth, extra = width % iwidth;

        // draw the full copies of the image
        for (int ii = 0; ii < tcount; ii++) {
            g.drawImage(image, x, y, null);
            x += iwidth;
        }

        // clip the final blit
        if (extra > 0) {
            Shape oclip = g.getClip();
            g.clipRect(x, y, extra, iheight);
            g.drawImage(image, x, y, null);
            g.setClip(oclip);
        }
    }

    /**
     * Paints multiple copies of the supplied image using the supplied
     * graphics context such that the requested height is filled with the
     * image.
     */
    public static void tileImageDown (Graphics g, Image image,
                                      int x, int y, int height)
    {
        int iwidth = image.getWidth(null), iheight = image.getHeight(null);
        int tcount = height/iheight, extra = height % iheight;

        // draw the full copies of the image
        for (int ii = 0; ii < tcount; ii++) {
            g.drawImage(image, x, y, null);
            y += iheight;
        }

        // clip the final blit
        if (extra > 0) {
            Shape oclip = g.getClip();
            g.clipRect(x, y, iwidth, extra);
            g.drawImage(image, x, y, null);
            g.setClip(oclip);
        }
    }

    // Not fully added because we're not using it anywhere, plus
    // it's probably a little sketchy to create Area objects with all
    // this pixely data.
    // Also, the Area was getting zeroed out when it was translated. Something
    // to look into someday if anyone wants to use this method.
//    /**
//     * Creates a mask that is opaque in the non-transparent areas of the
//     * source image.
//     */
//    public static Area createImageMask (BufferedImage src)
//    {
//        Raster srcdata = src.getData(); 
//        int wid = src.getWidth(), hei = src.getHeight();
//        Log.info("creating area of (" + wid + ", " + hei + ")");
//        Area a = new Area(new Rectangle(wid, hei));
//        Rectangle r = new Rectangle(1, 1);
//
//        for (int yy=0; yy < hei; yy++) {
//            for (int xx=0; xx < wid; xx++) {
//                if (srcdata.getSample(xx, yy, 0) == 0) {
//                    r.setLocation(xx, yy);
//                    a.subtract(new Area(r));
//                }
//            }
//        }
//
//        return a;
//    }

    /**
     * Creates and returns a new image consisting of the supplied image
     * traced with the given color and thickness.
     */
    public static BufferedImage createTracedImage (
        ImageManager imgr, BufferedImage src, Color tcolor, int thickness)
    {
        return createTracedImage(imgr, src, tcolor, thickness, 1.0f, 1.0f);
    }

    /**
     * Creates and returns a new image consisting of the supplied image
     * traced with the given color, thickness and alpha transparency.
     */
    public static BufferedImage createTracedImage (
        ImageManager imgr, BufferedImage src, Color tcolor, int thickness,
        float startAlpha, float endAlpha)
    {
        Raster srcdata = src.getData(); 
        int wid = src.getWidth(), hei = src.getHeight();

        // create the destination image
        BufferedImage dest = imgr.createImage(
            wid, hei, Transparency.TRANSLUCENT);

        // prepare various bits of working data
        int srcTrans = src.getColorModel().getTransparency();
        int[] tpixel = new int[] {
{'fraction_non_alphanumeric': 0.10449808008776741, 'fraction_numerical': 0.006472846955567746, 'mean_word_length': 1.9764897959183674, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1686008', 'n_tokens_mistral': 5351, 'n_tokens_neox': 5113, 'n_words': 1727}