content: package com.agentdid127.resourcepack.library.utilities;
  
  import javax.imageio.ImageIO;
  import java.awt.*;
  import java.awt.geom.AffineTransform;
  import java.awt.image.BufferedImage;
  import java.io.IOException;
  import java.nio.file.Path;
  
  public class ImageConverter {
      protected int imageWidth;
      protected int imageHeight;
      protected int defaultW;
      protected int defaultH;
      protected BufferedImage image;
      protected BufferedImage newImage;
      protected Path location;
      protected Graphics2D g2d;
  
      /**
       * Default Constructor
       *
       * @param defaultWIn
       * @param defaultHIn
       * @param locationIn
       * @throws IOException
       */
      public ImageConverter(Integer defaultWIn, Integer defaultHIn, Path locationIn) throws IOException {
          image = ImageIO.read(locationIn.toFile());
          location = locationIn;
          defaultW = defaultWIn == null ? image.getWidth() : defaultWIn;
          defaultH = defaultHIn == null ? image.getHeight() : defaultHIn;
          imageWidth = image.getWidth();
          imageHeight = image.getHeight();
  
          // check if widths and heights are legal.
          if (defaultW == 0 || defaultH == 0) {
              throw new IllegalStateException("Image must have a default width or height greater than zero.");
          }
  
          if (!fileIsPowerOfTwo()) {
              Logger.debug("Image (" + image.getWidth() + "x" + image.getHeight() + ") '" + locationIn.getFileName()
                      + "' resolution size is not a power of 2. Converting to be so.");
  
              int fixed_width = (int) Math.ceil(Math.log(image.getWidth()) / Math.log(2));
              if (fixed_width < 1) {
                  fixed_width = 1;
              }
  
              int fixed_height = (int) Math.ceil(Math.log(image.getHeight()) / Math.log(2));
              if (fixed_height < 1) {
                  fixed_height = 1;
              }
  
              newImage = new BufferedImage(fixed_width, fixed_height, image.getType());
              Graphics2D g = newImage.createGraphics();
              g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
              g.drawImage(image, 0, 0, fixed_width, fixed_height, 0, 0, imageWidth, imageHeight, null);
              g.dispose();
          } else {
              newImage = image;
          }
      }
  
      /**
       * Save a section of the image to a new image.
       *
       * @param sx
       * @param sy
       * @param width
       * @param height
       * @param path
       * @throws IOException
       */
      public void saveSlice(int sx, int sy, int width, int height, Path path) throws IOException {
          BufferedImage original = this.newImage;
          Graphics2D graphics = this.g2d;
          newImage(width, height);
          subImage(sx, sy, sx + width, sy + height);
          store(path);
          this.newImage = original;
          this.g2d = graphics;
      }
  
      /**
       * Fill a spot with nothing.
       *
       * @param x
       * @param y
       * @param width
       * @param height
       */
      public void fillEmpty(int x, int y, int width, int height) {
          newImage((int) (imageWidth / getWidthMultiplier()), (int) (imageHeight / getHeightMultiplier()));
          g2d.drawImage(image, 0, 0, imageWidth, imageHeight, null);
          g2d.setComposite(AlphaComposite.Clear);
          g2d.fillRect(scaleX(x), scaleY(y), scaleWidth(width), scaleHeight(height));
          g2d.setComposite(AlphaComposite.Src);
          image = newImage;
      }
  
      /**
       * Set the current image.
       *
       * @param defaultWIn
       * @param defaultHIn
       * @throws IOException
       */
      public void setImage(int defaultWIn, int defaultHIn) throws IOException {
          if (!imageIsPowerOfTwo(newImage)) {
              Logger.debug("Image '" + location.getFileName() + "' is not a power of 2");
              return;
          }
  
          image = newImage;
          defaultW = defaultWIn;
          defaultH = defaultHIn;
          imageWidth = image.getWidth();
          imageHeight = image.getHeight();
      }
  
      /**
       * Creates a new Image to store
       *
       * @param newWidth
       * @param newHeight
       */
      public void newImage(int newWidth, int newHeight, int type) {
          if (newWidth == 0 || newHeight == 0) {
              throw new IllegalArgumentException("Width and height must be greater than zero!");
          }
  
          newImage = new BufferedImage(scaleWidth(newWidth), scaleHeight(newHeight), type);
          g2d = (Graphics2D) newImage.getGraphics();
      }
  
      public void newImage(int newWidth, int newHeight) {
          newImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB);
      }
  
      /**
       * Add a image to the current image.
       *
       * @param imagePath
       * @param x
       * @param y
       * @throws IOException
       */
      public void addImage(Path imagePath, int x, int y) throws IOException {
          if (imagePath.toFile().exists()) {
              BufferedImage image = ImageIO.read(imagePath.toFile());
              g2d.drawImage(image, scaleX(x), scaleY(y), null);
          }
      }
  
      /**
       * Takes part of an image and stores it in the new image
       *
       * @param x
       * @param y
       * @param x2
       * @param y2
       * @param storeX
       * @param storeY
       */
      public void subImage(int x, int y, int x2, int y2, int storeX, int storeY) {
          double scaleW = getWidthMultiplier();
          double scaleH = getHeightMultiplier();
          int width2 = (int) (x2 * scaleW - x * scaleW);
          int height2 = (int) (y2 * scaleH - y * scaleH);
          BufferedImage part = getSubImage(scaleX(x), scaleY(y), width2, height2);
          g2d.drawImage(part, Math.round((float) (storeX * scaleW)), Math.round((float) (storeY * scaleH)), null);
      }
  
      /**
       * Takes part of an image and stores it in the new image
       *
       * @param x
       * @param y
       * @param x2
       * @param y2
       */
      public void subImage(int x, int y, int x2, int y2) {
          subImage(x, y, x2, y2, 0, 0);
      }
  
      /**
       * Takes a part of an image and flips it either horizontally or vertically
       *
       * @param x
       * @param y
       * @param x2
       * @param y2
       * @param storeX
       * @param storeY
       * @param flip
       */
      public void subImage(int x, int y, int x2, int y2, int storeX, int storeY, boolean flip) {
          double scaleW = getWidthMultiplier();
          double scaleH = getHeightMultiplier();
          int width2 = (int) (x2 * scaleW - x * scaleW);
          int height2 = (int) (y2 * scaleH - y * scaleH);
          BufferedImage part = getSubImage(scaleX(x), scaleY(y), width2, height2);
          g2d.drawImage(createFlipped(part, flip), Math.round((float) (storeX * scaleW)), Math.round((float) (storeY * scaleH)), null);
      }
  
      /**
       * Only allows for the number 1 and flips it both horizontally and vertically
       *
       * @param x
       * @param y
       * @param x2
       * @param y2
       * @param storeX
       * @param storeY
       * @param flip
       */
      public void subImage(int x, int y, int x2, int y2, int storeX, int storeY, int flip) {
          double scaleW = getWidthMultiplier();
          double scaleH = getHeightMultiplier();
  
          int width2 = (int) (x2 * scaleW - x * scaleW);
          int height2 = (int) (y2 * scaleH - y * scaleH);
          int x3 = (int) (x * scaleW);
          int y3 = (int) (y * scaleH);
  
          BufferedImage part = getSubImage(x3, y3, width2, height2);
          g2d.drawImage(createFlipped(part, flip), Math.round((float) (storeX * scaleW)), Math.round((float) (storeY * scaleH)), null);
      }
  
      public void subImageSized(int x, int y, int width, int height, int storeX, int storeY) {
          subImage(x, y, x + width, y + height, storeX, storeY);
      }
  
      public void subImageSized(int x, int y, int width, int height) {
          subImage(x, y, x + width, y + height, 0, 0);
      }
  
      /**
       * Recolor the grayscale image.
       *
       * @param color
       */
      public void colorizeGrayscale(Color color) {
          this.newImage(this.defaultW, this.defaultH);
          this.g2d.drawImage(this.image, 0, 0, null);
          for (int y = 0; y < this.getHeight(); y++) {
              for (int x = 0; x < this.getWidth(); x++) {
                  int rgba = this.newImage.getRGB(x, y);
                  int alpha = (rgba >> 24) & 0xFF;
                  if (alpha == 0) {
                      continue;
                  }
  
                  int grayscaleValue = (rgba >> 16) & 0xFF;
                  int red = (grayscaleValue * color.getRed()) / 255;
                  int green = (grayscaleValue * color.getGreen()) / 255;
                  int blue = (grayscaleValue * color.getBlue()) / 255;
                  this.newImage.setRGB(x, y, (alpha << 24) | (red << 16) | (green << 8) | blue);
              }
          }
      }
  
      /**
       * Grayscale the image using the NTSC grayscale formula
       */
      public void grayscale() {
          BufferedImage gray = new BufferedImage(this.newImage.getWidth(), this.newImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
          gray.createGraphics().drawImage(this.image, 0, 0, null);
          for (int y = 0; y < gray.getHeight(); y++) {
              for (int x = 0; x < gray.getWidth(); x++) {
                  int rgba = gray.getRGB(x, y);
                  int alpha = (rgba >> 24) & 0xFF;
                  int red = (rgba >> 16) & 0xFF;
                  int green = (rgba >> 8) & 0xFF;
                  int blue = rgba & 0xFF;
                  int average = (int) ((0.299 * red) + (0.587 * green) + (0.114 * blue));
                  gray.setRGB(x, y, (alpha << 24) | (average << 16) | (average << 8) | average);
              }
          }
          this.newImage = gray;
      }
  
      /**
       * Flip The Image
       *
       * @param image
       * @param flip
       * @return BufferedImage
       */
      private static BufferedImage createFlipped(BufferedImage image, int flip) {
          AffineTransform at = new AffineTransform();
          if (flip != 1) {
              return image;
          } else {
              at.concatenate(AffineTransform.getScaleInstance(1, -1));
              at.concatenate(AffineTransform.getTranslateInstance(0, -image.getHeight()));
              at.concatenate(AffineTransform.getScaleInstance(-1, 1));
              at.concatenate(AffineTransform.getTranslateInstance(-image.getWidth(), 0));
              return createTransformed(image, at);
          }
      }
  
      /**
       * Flip The Image
       *
       * @param image
       * @param flip
       * @return BufferedImage
       */
      private static BufferedImage createFlipped(BufferedImage image, boolean flip) {
          AffineTransform at = new AffineTransform();
          if (flip) {
              at.concatenate(AffineTransform.getScaleInstance(1, -1));
              at.concatenate(AffineTransform.getTranslateInstance(0, -image.getHeight()));
          } else {
              at.concatenate(AffineTransform.getScaleInstance(-1, 1));
              at.concatenate(AffineTransform.getTranslateInstance(-image.getWidth(), 0));
          }
          return createTransformed(image, at);
      }
  
      /**
       * Transforms The BufferedImage
       *
       * @param image
       * @param at
       * @return BufferedImage
       */
      private static BufferedImage createTransformed(BufferedImage image, AffineTransform at) {
          BufferedImage newImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB);
          Graphics2D g = newImage.createGraphics();
          g.transform(at);
          g.drawImage(image, 0, 0, null);
          g.dispose();
          return newImage;
      }
  
      /**
       * Copy pixels to a new image with a new alpha & ignoring transparent pixels
       *
       * @param inputImage
       * @param outImage
       * @param alphaNew
       */
      private void copyPixels(BufferedImage inputImage, BufferedImage outImage, Integer alphaNew) {
          BufferedImage image = new BufferedImage(inputImage.getWidth(), inputImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
          image.getGraphics().drawImage(inputImage, 0, 0, null);
          for (int y = 0; y < image.getHeight(); y++) {
              for (int x = 0; x < image.getWidth(); x++) {
                  int rgba = image.getRGB(x, y);
                  int alpha = (rgba >> 24) & 0xFF;
                  int red = (rgba >> 16) & 0xFF;
                  int green = (rgba >> 8) & 0xFF;
                  int blue = rgba & 0xFF;
                  if ((red + blue + green + alpha) == 0)
                      continue;
                  if (alphaNew != null)
                      alpha = alphaNew;
                  outImage.setRGB(x, y, (alpha << 24) | (red << 16) | (green << 8) | blue);
              }
          }
      }
  
      /**
       * Copy pixels to a new image
       *
       * @param inputImage
       * @param outImage
       * @throws IOException
       */
      private void copyPixels(BufferedImage inputImage, BufferedImage outImage) throws IOException {
          copyPixels(inputImage, outImage, null);
      }
  
      /**
       * Place a image in the background using transparency
       *
       * @param background
       * @param alpha
       * @throws IOException
       */
      public void backgroundImage(Path background, int alpha) throws IOException {
          this.newImage(defaultW, defaultH);
          BufferedImage backgroundImage = ImageIO.read(background.toFile());
          this.copyPixels(backgroundImage, newImage, alpha);
          this.copyPixels(image, newImage);
      }
  
      /**
       * Place a image in the background using transparency
       *
       * @param backgroundImage
       * @param alpha
       * @throws IOException
       */
      public void backgroundImage(BufferedImage backgroundImage, int alpha) throws IOException {
          this.newImage(defaultW, defaultH);
          this.copyPixels(backgroundImage, newImage, alpha);
          this.copyPixels(image, newImage);
      }
  
      /**
       * Save the image as the provided type at the provided location.
       *
       * @param locationIn
       * @param type
       * @return boolean
       * @throws IOException
       */
      public boolean store(Path locationIn, String type) throws IOException {
          ImageIO.write(newImage, type, locationIn.toFile());
          return true;
      }
  
      /**
       * Save the image at the provided location.
       *
       * @param locationIn
       * @return boolean
       * @throws IOException
       */
      public boolean store(Path locationIn) throws IOException {
          return store(locationIn, "png");
      }
  
      /**
       * Save the image.
       *
       * @return boolean
       * @throws IOException
       */
      public boolean store() throws IOException {
          return store(location);
      }
  
      /**
       * Get a sub-part of the image using a rectangle.
       *
       * @param x
       * @param y
       * @param width
       * @param height
       * @return BufferedImage
       */
      private BufferedImage getSubImage(int x, int y, int width, int height) {
          return image.getSubimage(x, y, width, height);
      }
  
      /**
       * Get the Image Width
       *
       * @return int
       */
      public int getWidth() {
          return imageWidth;
      }
  
      /**
       * Get the Image Height
       *
       * @return int
       */
      public int getHeight() {
          return imageHeight;
      }
  
      /**
       * Get the Width Scale Multiplier (imageWidth/defaultWidth)
       *
       * @return double
       */
      public double getWidthMultiplier() {
          return (double) imageWidth / (double) defaultW;
      }
  
      /**
       * Get the Height Scale Multiplier (imageHeight/defaultHeight)
       *
       * @return double
       */
      public double getHeightMultiplier() {
          return (double) imageHeight / (double) defaultH;
      }
  
      public int scaleX(int x) {
          return (int) (x * getWidthMultiplier());
      }
  
      public int scaleY(int y) {
          return (int) (y * getHeightMultiplier());
      }
  
      public int scaleWidth(int width) {
          return (int) (width * getWidthMultiplier());
      }
  
      public int scaleHeight(int height) {
          return (int) (height * getHeightMultiplier());
      }
  
      public boolean imageIsPowerOfTwo(BufferedImage image) {
          return (isPowerOfTwo(image.getWidth()) && isPowerOfTwo(image.getHeight()));
      }
  
      public boolean fileIsPowerOfTwo() {
          return imageIsPowerOfTwo(image);
      }
  
      // Detects if file is a power of two.
      private boolean isPowerOfTwo(int n) {
          return n > 0 && n == Math.pow(2, Math.round(Math.log(n) / Math.log(2)));
      }
  
      /**
       * Returns true if the image is square.
       *
       * @return boolean
       */
      public boolean isSquare() {
          return imageWidth == imageHeight;
      }
  }
  