= Updated YEET to generic version
This commit is contained in:
@@ -1,24 +1,8 @@
|
|||||||
package commands;
|
package commands;
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.awt.image.DataBufferByte;
|
|
||||||
import java.awt.image.RenderedImage;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Calendar;
|
|
||||||
import java.util.Iterator;
|
|
||||||
|
|
||||||
import javax.imageio.IIOException;
|
|
||||||
import javax.imageio.IIOImage;
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import javax.imageio.ImageTypeSpecifier;
|
|
||||||
import javax.imageio.ImageWriteParam;
|
|
||||||
import javax.imageio.ImageWriter;
|
|
||||||
import javax.imageio.metadata.IIOMetadata;
|
|
||||||
import javax.imageio.metadata.IIOMetadataNode;
|
|
||||||
import javax.imageio.stream.FileImageOutputStream;
|
|
||||||
import javax.imageio.stream.ImageOutputStream;
|
|
||||||
|
|
||||||
import core.Command;
|
import core.Command;
|
||||||
import core.Localizer;
|
import core.Localizer;
|
||||||
import dataStructures.KittyChannel;
|
import dataStructures.KittyChannel;
|
||||||
@@ -28,6 +12,7 @@ import dataStructures.KittyRole;
|
|||||||
import dataStructures.KittyUser;
|
import dataStructures.KittyUser;
|
||||||
import dataStructures.Response;
|
import dataStructures.Response;
|
||||||
import dataStructures.UserInput;
|
import dataStructures.UserInput;
|
||||||
|
import utils.ImageOverlayBuilder;
|
||||||
import utils.ImageUtils;
|
import utils.ImageUtils;
|
||||||
|
|
||||||
public class CommandYeet extends Command
|
public class CommandYeet extends Command
|
||||||
@@ -73,7 +58,8 @@ public class CommandYeet extends Command
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
yeeteeFile = new File(yeeteeFilename);
|
yeeteeFile = new File(yeeteeFilename);
|
||||||
YEET(ImageIO.read(yeeteeFile), name);
|
ImageOverlayBuilder builder = new ImageOverlayBuilder("assets/yeet/frames/", "yeet ", 24, 18);
|
||||||
|
builder.Overlay(ImageIO.read(yeeteeFile), name);
|
||||||
}
|
}
|
||||||
catch (IOException e)
|
catch (IOException e)
|
||||||
{
|
{
|
||||||
@@ -87,251 +73,4 @@ public class CommandYeet extends Command
|
|||||||
ImageUtils.BlockingFileDelete(yeetFile);
|
ImageUtils.BlockingFileDelete(yeetFile);
|
||||||
ImageUtils.BlockingFileDelete(yeeteeFile);
|
ImageUtils.BlockingFileDelete(yeeteeFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lets get some trash established for the yeet
|
|
||||||
public static final boolean VERBOSE = false;
|
|
||||||
public static final int NONE = -1;
|
|
||||||
public static final int PIXEL_BYTE_LENGTH = 4; // 32-bit PNG, RGBA
|
|
||||||
public static final String YEET_BASE_PATH = "assets/yeet/frames/";
|
|
||||||
public static final int YEET_BASE_SIZE = 24;
|
|
||||||
public static final int YEET_FPS = 18;
|
|
||||||
|
|
||||||
// Some small logging functions... cause we're professionals...
|
|
||||||
public static void Verbose(String str) { if(VERBOSE) Log("[Verbose] " + str); }
|
|
||||||
public static void Log(String str) { System.out.println("[Log] " + str); }
|
|
||||||
public static void Warn(String str) { System.out.println("[Warn] " + str); }
|
|
||||||
public static void Error(String str) { System.out.println("[Error] " + str); }
|
|
||||||
|
|
||||||
// Performs the Y E E T (image overlay per frame specified, catches IO errors as a note.)
|
|
||||||
public static void YEET(BufferedImage overlay, String outpath)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
long start = Calendar.getInstance().getTimeInMillis();
|
|
||||||
|
|
||||||
ProcessYeet(overlay, outpath);
|
|
||||||
|
|
||||||
long end = Calendar.getInstance().getTimeInMillis();
|
|
||||||
Log("Took " + (end - start) + "ms");
|
|
||||||
}
|
|
||||||
catch (IOException e)
|
|
||||||
{
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Processing the image frames to construct a gif. Relies on (0,255,0) pixel for image center specification.
|
|
||||||
public static void ProcessYeet(BufferedImage overlay, String outfileName) throws IOException
|
|
||||||
{
|
|
||||||
BufferedImage[] frames = new BufferedImage[YEET_BASE_SIZE];
|
|
||||||
|
|
||||||
for(int i = 0; i < YEET_BASE_SIZE; ++i)
|
|
||||||
{
|
|
||||||
BufferedImage out = CombineImages(YEET_BASE_PATH + "yeet " + i + ".png", overlay);
|
|
||||||
|
|
||||||
if(out != null)
|
|
||||||
frames[i] = out;
|
|
||||||
else
|
|
||||||
Error("There was an issue with frame " + i);
|
|
||||||
}
|
|
||||||
|
|
||||||
Verbose("Processed " + YEET_BASE_SIZE + " frames, writing...");
|
|
||||||
ImageOutputStream output = new FileImageOutputStream(new File(outfileName));
|
|
||||||
GifSequenceWriter writer = new GifSequenceWriter(output, frames[0].getType(), YEET_FPS, true);
|
|
||||||
|
|
||||||
for(int i = 0; i < YEET_BASE_SIZE; ++i)
|
|
||||||
writer.writeToSequence(frames[i]);
|
|
||||||
|
|
||||||
writer.close();
|
|
||||||
output.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Performs an overlay at a pixel location. We're making some assumptions here, mostly that there is going
|
|
||||||
// to be an RGBA-32-bit encoded PNG image read in for our parsing purposes. We then look for a 0, 255, 0
|
|
||||||
// green pixel on the base frame to apply the overlay buffer to, centered.
|
|
||||||
public static BufferedImage CombineImages(String pathBase, BufferedImage overlay) throws IOException
|
|
||||||
{
|
|
||||||
// Acquire images
|
|
||||||
BufferedImage imageBase = null;
|
|
||||||
BufferedImage imageOverlay = overlay;
|
|
||||||
|
|
||||||
imageBase = ImageIO.read(new File(pathBase));
|
|
||||||
|
|
||||||
// Grab data we know won't change. As a side note, I discovered you can get specific pixels a bit
|
|
||||||
// differently later, but it was too late, I wrote the pixel specific code already.
|
|
||||||
final byte[] pixelsBase = ((DataBufferByte) imageBase.getRaster().getDataBuffer()).getData();
|
|
||||||
final int baseWidth = imageBase.getWidth();
|
|
||||||
final int overlayWidth = imageOverlay.getWidth();
|
|
||||||
final int baseHeight = imageBase.getHeight();
|
|
||||||
final int overlayHeight = imageOverlay.getHeight();
|
|
||||||
|
|
||||||
// Warn about odd sizing when applicable
|
|
||||||
if(overlayHeight > baseHeight || overlayWidth > baseWidth)
|
|
||||||
Warn("Size mismatch, overlay is larger. May function, but not supported.");
|
|
||||||
|
|
||||||
// Skim base picture for solid green pixel to use as target center
|
|
||||||
int targetX = NONE;
|
|
||||||
int targetY = NONE;
|
|
||||||
for(int i = 0; i < pixelsBase.length; i += PIXEL_BYTE_LENGTH)
|
|
||||||
{
|
|
||||||
final int x = ((i / PIXEL_BYTE_LENGTH) % baseWidth);
|
|
||||||
final int y = ((i / PIXEL_BYTE_LENGTH) / baseWidth);
|
|
||||||
|
|
||||||
final int r = (pixelsBase[i]);
|
|
||||||
final int g = (pixelsBase[i + 1]);
|
|
||||||
final int b = (pixelsBase[i + 2]);
|
|
||||||
//final int a = (pixelsBase[i + 3]);
|
|
||||||
|
|
||||||
// Max contrast in 32-bit PNG for green is no red no blue, max green (ratio), max alpha (implied 0 here)
|
|
||||||
if(r == -1 && g == 0 && b == -1)
|
|
||||||
{
|
|
||||||
//Log("Found target pixel at x: " + x + " y: " + y);
|
|
||||||
targetX = x;
|
|
||||||
targetY = y;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip overlay if we have no target location
|
|
||||||
if(targetX == NONE || targetY == NONE)
|
|
||||||
{
|
|
||||||
Verbose("No overlay required for this frame.");
|
|
||||||
return imageBase;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Re-create a new byte array and establish overlay bounds
|
|
||||||
final int left = targetX - overlayWidth / 2;
|
|
||||||
final int right = targetX + overlayWidth / 2;
|
|
||||||
final int top = targetY - overlayHeight / 2;
|
|
||||||
final int bottom = targetY + overlayHeight / 2;
|
|
||||||
|
|
||||||
// Apply overlay when appropriate
|
|
||||||
for(int i = 0; i < pixelsBase.length; i += PIXEL_BYTE_LENGTH)
|
|
||||||
{
|
|
||||||
final int x = ((i / PIXEL_BYTE_LENGTH) % baseWidth);
|
|
||||||
final int y = ((i / PIXEL_BYTE_LENGTH) / baseWidth);
|
|
||||||
|
|
||||||
// If we're within the bounds of where the overlay picture should go...
|
|
||||||
if(x > left && x < right && y > top && y < bottom)
|
|
||||||
{
|
|
||||||
final int overlayX = x - left;
|
|
||||||
final int overlayY = y - top;
|
|
||||||
|
|
||||||
// Skip out of bounds pixels
|
|
||||||
if(overlayX < 0 || overlayY < 0 || overlayX >= baseWidth || overlayY >= baseHeight)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Re-write bytes as overlay
|
|
||||||
imageBase.setRGB(x, y, imageOverlay.getRGB(overlayX, overlayY));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Give the frame back
|
|
||||||
return imageBase;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// GifSequenceWriter.java
|
|
||||||
//
|
|
||||||
// Created by Elliot Kroo on 2009-04-25,
|
|
||||||
// (Small modifications by wisp in 2018)
|
|
||||||
//
|
|
||||||
// This work is licensed under the Creative Commons Attribution 3.0 Unported License.
|
|
||||||
// To view a copy of this license visit http://creativecommons.org/licenses/by/3.0/
|
|
||||||
// NOTE(wisp): This is Elliot's original license
|
|
||||||
public static class GifSequenceWriter
|
|
||||||
{
|
|
||||||
protected ImageWriter gifWriter;
|
|
||||||
protected ImageWriteParam imageWriteParam;
|
|
||||||
protected IIOMetadata imageMetaData;
|
|
||||||
|
|
||||||
// NOTE: This is limited to things that divide into 1000 cleanly as multiples of 10.
|
|
||||||
public GifSequenceWriter(ImageOutputStream outputStream, int imageType, int framesPerSecond, boolean isLooping) throws IIOException, IOException
|
|
||||||
{
|
|
||||||
gifWriter = getWriter();
|
|
||||||
imageWriteParam = gifWriter.getDefaultWriteParam();
|
|
||||||
ImageTypeSpecifier imageTypeSpecifier = ImageTypeSpecifier.createFromBufferedImageType(imageType);
|
|
||||||
|
|
||||||
imageMetaData = gifWriter.getDefaultImageMetadata(imageTypeSpecifier, imageWriteParam);
|
|
||||||
|
|
||||||
String metaFormatName = imageMetaData.getNativeMetadataFormatName();
|
|
||||||
|
|
||||||
IIOMetadataNode root = (IIOMetadataNode) imageMetaData.getAsTree(metaFormatName);
|
|
||||||
IIOMetadataNode graphicsControlExtensionNode = getNode(root, "GraphicControlExtension");
|
|
||||||
graphicsControlExtensionNode.setAttribute("disposalMethod", "none");
|
|
||||||
graphicsControlExtensionNode.setAttribute("userInputFlag", "FALSE");
|
|
||||||
graphicsControlExtensionNode.setAttribute("transparentColorFlag", "FALSE");
|
|
||||||
graphicsControlExtensionNode.setAttribute("delayTime", "" + (int) ((1000.0 / framesPerSecond) / 10.0)); // THis line isn't super well honored
|
|
||||||
graphicsControlExtensionNode.setAttribute("transparentColorIndex","0");
|
|
||||||
|
|
||||||
IIOMetadataNode appEntensionsNode = getNode( root, "ApplicationExtensions");
|
|
||||||
IIOMetadataNode child = new IIOMetadataNode("ApplicationExtension");
|
|
||||||
child.setAttribute("applicationID", "NETSCAPE");
|
|
||||||
child.setAttribute("authenticationCode", "2.0");
|
|
||||||
|
|
||||||
int loopValue = isLooping ? 0 : 1;
|
|
||||||
child.setUserObject(new byte[]{ 0x1, (byte) (loopValue & 0xFF), (byte) ((loopValue >> 8) & 0xFF)});
|
|
||||||
appEntensionsNode.appendChild(child);
|
|
||||||
|
|
||||||
imageMetaData.setFromTree(metaFormatName, root);
|
|
||||||
|
|
||||||
gifWriter.setOutput(outputStream);
|
|
||||||
gifWriter.prepareWriteSequence(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeToSequence(RenderedImage img) throws IOException
|
|
||||||
{
|
|
||||||
gifWriter.writeToSequence(new IIOImage(img, null, imageMetaData), imageWriteParam);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Close this GifSequenceWriter object. This does not close the underlying
|
|
||||||
* stream, just finishes off the GIF.
|
|
||||||
*/
|
|
||||||
public void close() throws IOException
|
|
||||||
{
|
|
||||||
gifWriter.endWriteSequence();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the first available GIF ImageWriter using
|
|
||||||
* ImageIO.getImageWritersBySuffix("gif").
|
|
||||||
*
|
|
||||||
* @return a GIF ImageWriter object
|
|
||||||
* @throws IIOException if no GIF image writers are returned
|
|
||||||
*/
|
|
||||||
private static ImageWriter getWriter() throws IIOException
|
|
||||||
{
|
|
||||||
Iterator<ImageWriter> iter = ImageIO.getImageWritersBySuffix("gif");
|
|
||||||
|
|
||||||
if(!iter.hasNext())
|
|
||||||
throw new IIOException("No GIF Image Writers Exist");
|
|
||||||
else
|
|
||||||
return iter.next();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an existing child node, or creates and returns a new child node (if
|
|
||||||
* the requested node does not exist).
|
|
||||||
*
|
|
||||||
* @param rootNode the <tt>IIOMetadataNode</tt> to search for the child node.
|
|
||||||
* @param nodeName the name of the child node.
|
|
||||||
*
|
|
||||||
* @return the child node, if found or a new node created with the given name.
|
|
||||||
*/
|
|
||||||
private static IIOMetadataNode getNode(IIOMetadataNode rootNode, String nodeName)
|
|
||||||
{
|
|
||||||
int nodeCount = rootNode.getLength();
|
|
||||||
for (int i = 0; i < nodeCount; i++)
|
|
||||||
{
|
|
||||||
if (rootNode.item(i).getNodeName().compareToIgnoreCase(nodeName) == 0)
|
|
||||||
return((IIOMetadataNode) rootNode.item(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
IIOMetadataNode node = new IIOMetadataNode(nodeName);
|
|
||||||
rootNode.appendChild(node);
|
|
||||||
|
|
||||||
return(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,14 +21,15 @@ import javax.imageio.stream.ImageOutputStream;
|
|||||||
|
|
||||||
// A class that overlays an image based on a series of frames that will be converted into a gif.
|
// A class that overlays an image based on a series of frames that will be converted into a gif.
|
||||||
// The class targets a pixel with max Green value and no red or blue that's fully opaque (0, 255, 0)
|
// The class targets a pixel with max Green value and no red or blue that's fully opaque (0, 255, 0)
|
||||||
// and then places the center of the overlay image on the specified location.
|
// and then places the center of the overlay image on the specified location. Must be 32-bit PNGs with RGBA format.
|
||||||
public class ImageOverlayHelper
|
public class ImageOverlayBuilder
|
||||||
{
|
{
|
||||||
// Lets get some trash established for the yeet
|
// Lets get some trash established for the yeet
|
||||||
public static final boolean VERBOSE = false;
|
public static final boolean VERBOSE = false;
|
||||||
public static final int NONE = -1;
|
public static final int NONE = -1;
|
||||||
public static final int PIXEL_BYTE_LENGTH = 4; // 32-bit PNG, RGBA
|
public static final int PIXEL_BYTE_LENGTH = 4; // 32-bit PNG, RGBA
|
||||||
public static final int FPS = 18; // This doesn't quite line up, multiples of 10 are as specific as it gets.
|
public static final int DEFAULT_FPS = 18;
|
||||||
|
public static final String FRAME_FILE_EXTENSION = ".png";
|
||||||
public static final String LOG_HEADER = "[Overlay] ";
|
public static final String LOG_HEADER = "[Overlay] ";
|
||||||
|
|
||||||
// Logging intermediate functions
|
// Logging intermediate functions
|
||||||
@@ -41,13 +42,21 @@ public class ImageOverlayHelper
|
|||||||
private final String basePath; // The folder location of the files
|
private final String basePath; // The folder location of the files
|
||||||
private final String baseName; // This is the consistent part of the filename of each frame, for example "image "
|
private final String baseName; // This is the consistent part of the filename of each frame, for example "image "
|
||||||
private final int frameCount; // The number of frames in the image
|
private final int frameCount; // The number of frames in the image
|
||||||
|
private final int fps; // This doesn't quite line up, multiples of 10 are as specific as it gets.
|
||||||
|
|
||||||
// Constructor
|
// Required minimal constructor
|
||||||
public ImageOverlayHelper(String basePath, String baseName, int frameCount)
|
public ImageOverlayBuilder(String basePath, String baseName, int frameCount)
|
||||||
|
{
|
||||||
|
this(basePath, baseName, frameCount, DEFAULT_FPS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructor with framerate specification
|
||||||
|
public ImageOverlayBuilder(String basePath, String baseName, int frameCount, int fps)
|
||||||
{
|
{
|
||||||
this.basePath = basePath;
|
this.basePath = basePath;
|
||||||
this.baseName = baseName;
|
this.baseName = baseName;
|
||||||
this.frameCount = frameCount;
|
this.frameCount = frameCount;
|
||||||
|
this.fps = fps;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Performs a per-frame overlay, catches IO errors as a note.)
|
// Performs a per-frame overlay, catches IO errors as a note.)
|
||||||
@@ -69,13 +78,13 @@ public class ImageOverlayHelper
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Processing the image frames to construct a gif. Relies on (0,255,0) pixel for image center specification.
|
// Processing the image frames to construct a gif. Relies on (0,255,0) pixel for image center specification.
|
||||||
public void ProcessOverlay(BufferedImage overlay, String outfileName) throws IOException
|
private void ProcessOverlay(BufferedImage overlay, String outfileName) throws IOException
|
||||||
{
|
{
|
||||||
BufferedImage[] frames = new BufferedImage[frameCount];
|
BufferedImage[] frames = new BufferedImage[frameCount];
|
||||||
|
|
||||||
for(int i = 0; i < frameCount; ++i)
|
for(int i = 0; i < frameCount; ++i)
|
||||||
{
|
{
|
||||||
BufferedImage out = CombineImages(basePath + baseName + i + ".png", overlay);
|
BufferedImage out = CombineImages(basePath + baseName + i + FRAME_FILE_EXTENSION , overlay);
|
||||||
|
|
||||||
if(out != null)
|
if(out != null)
|
||||||
frames[i] = out;
|
frames[i] = out;
|
||||||
@@ -85,7 +94,7 @@ public class ImageOverlayHelper
|
|||||||
|
|
||||||
Verbose("Processed " + frameCount + " frames, writing...");
|
Verbose("Processed " + frameCount + " frames, writing...");
|
||||||
ImageOutputStream output = new FileImageOutputStream(new File(outfileName));
|
ImageOutputStream output = new FileImageOutputStream(new File(outfileName));
|
||||||
GifSequenceWriter writer = new GifSequenceWriter(output, frames[0].getType(), FPS, true);
|
GifSequenceWriter writer = new GifSequenceWriter(output, frames[0].getType(), fps, true);
|
||||||
|
|
||||||
for(int i = 0; i < frameCount; ++i)
|
for(int i = 0; i < frameCount; ++i)
|
||||||
writer.writeToSequence(frames[i]);
|
writer.writeToSequence(frames[i]);
|
||||||
@@ -97,7 +106,7 @@ public class ImageOverlayHelper
|
|||||||
// Performs an overlay at a pixel location. We're making some assumptions here, mostly that there is going
|
// Performs an overlay at a pixel location. We're making some assumptions here, mostly that there is going
|
||||||
// to be an RGBA-32-bit encoded PNG image read in for our parsing purposes. We then look for a 0, 255, 0
|
// to be an RGBA-32-bit encoded PNG image read in for our parsing purposes. We then look for a 0, 255, 0
|
||||||
// green pixel on the base frame to apply the overlay buffer to, centered.
|
// green pixel on the base frame to apply the overlay buffer to, centered.
|
||||||
public static BufferedImage CombineImages(String pathBase, BufferedImage overlay) throws IOException
|
private BufferedImage CombineImages(String pathBase, BufferedImage overlay) throws IOException
|
||||||
{
|
{
|
||||||
// Acquire images
|
// Acquire images
|
||||||
BufferedImage imageBase = null;
|
BufferedImage imageBase = null;
|
||||||
@@ -187,7 +196,7 @@ public class ImageOverlayHelper
|
|||||||
// This work is licensed under the Creative Commons Attribution 3.0 Unported License.
|
// This work is licensed under the Creative Commons Attribution 3.0 Unported License.
|
||||||
// To view a copy of this license visit http://creativecommons.org/licenses/by/3.0/
|
// To view a copy of this license visit http://creativecommons.org/licenses/by/3.0/
|
||||||
// NOTE: ^ This is Elliot's original license
|
// NOTE: ^ This is Elliot's original license
|
||||||
public static class GifSequenceWriter
|
private static class GifSequenceWriter
|
||||||
{
|
{
|
||||||
protected ImageWriter gifWriter;
|
protected ImageWriter gifWriter;
|
||||||
protected ImageWriteParam imageWriteParam;
|
protected ImageWriteParam imageWriteParam;
|
||||||
Reference in New Issue
Block a user