diff --git a/assets/teey/frames/teey 0.png b/assets/teey/frames/teey 0.png new file mode 100644 index 0000000..571a7b7 Binary files /dev/null and b/assets/teey/frames/teey 0.png differ diff --git a/assets/teey/frames/teey 1.png b/assets/teey/frames/teey 1.png new file mode 100644 index 0000000..c2ebcae Binary files /dev/null and b/assets/teey/frames/teey 1.png differ diff --git a/assets/teey/frames/teey 10.png b/assets/teey/frames/teey 10.png new file mode 100644 index 0000000..6d1ef61 Binary files /dev/null and b/assets/teey/frames/teey 10.png differ diff --git a/assets/teey/frames/teey 11.png b/assets/teey/frames/teey 11.png new file mode 100644 index 0000000..205094c Binary files /dev/null and b/assets/teey/frames/teey 11.png differ diff --git a/assets/teey/frames/teey 12.png b/assets/teey/frames/teey 12.png new file mode 100644 index 0000000..ff584a5 Binary files /dev/null and b/assets/teey/frames/teey 12.png differ diff --git a/assets/teey/frames/teey 13.png b/assets/teey/frames/teey 13.png new file mode 100644 index 0000000..d5d540b Binary files /dev/null and b/assets/teey/frames/teey 13.png differ diff --git a/assets/teey/frames/teey 14.png b/assets/teey/frames/teey 14.png new file mode 100644 index 0000000..911e2ce Binary files /dev/null and b/assets/teey/frames/teey 14.png differ diff --git a/assets/teey/frames/teey 15.png b/assets/teey/frames/teey 15.png new file mode 100644 index 0000000..68a0c93 Binary files /dev/null and b/assets/teey/frames/teey 15.png differ diff --git a/assets/teey/frames/teey 16.png b/assets/teey/frames/teey 16.png new file mode 100644 index 0000000..9cfe7c1 Binary files /dev/null and b/assets/teey/frames/teey 16.png differ diff --git a/assets/teey/frames/teey 17.png b/assets/teey/frames/teey 17.png new file mode 100644 index 0000000..0959b54 Binary files /dev/null and b/assets/teey/frames/teey 17.png differ diff --git a/assets/teey/frames/teey 18.png b/assets/teey/frames/teey 18.png new file mode 100644 index 0000000..febf7f7 Binary files /dev/null and b/assets/teey/frames/teey 18.png differ diff --git a/assets/teey/frames/teey 19.png b/assets/teey/frames/teey 19.png new file mode 100644 index 0000000..d1eaa98 Binary files /dev/null and b/assets/teey/frames/teey 19.png differ diff --git a/assets/teey/frames/teey 2.png b/assets/teey/frames/teey 2.png new file mode 100644 index 0000000..d3146fd Binary files /dev/null and b/assets/teey/frames/teey 2.png differ diff --git a/assets/teey/frames/teey 20.png b/assets/teey/frames/teey 20.png new file mode 100644 index 0000000..b6c4089 Binary files /dev/null and b/assets/teey/frames/teey 20.png differ diff --git a/assets/teey/frames/teey 21.png b/assets/teey/frames/teey 21.png new file mode 100644 index 0000000..e89dc8d Binary files /dev/null and b/assets/teey/frames/teey 21.png differ diff --git a/assets/teey/frames/teey 22.png b/assets/teey/frames/teey 22.png new file mode 100644 index 0000000..ca4ce04 Binary files /dev/null and b/assets/teey/frames/teey 22.png differ diff --git a/assets/teey/frames/teey 23.png b/assets/teey/frames/teey 23.png new file mode 100644 index 0000000..4646793 Binary files /dev/null and b/assets/teey/frames/teey 23.png differ diff --git a/assets/teey/frames/teey 3.png b/assets/teey/frames/teey 3.png new file mode 100644 index 0000000..2a21759 Binary files /dev/null and b/assets/teey/frames/teey 3.png differ diff --git a/assets/teey/frames/teey 4.png b/assets/teey/frames/teey 4.png new file mode 100644 index 0000000..657b6bc Binary files /dev/null and b/assets/teey/frames/teey 4.png differ diff --git a/assets/teey/frames/teey 5.png b/assets/teey/frames/teey 5.png new file mode 100644 index 0000000..3a19846 Binary files /dev/null and b/assets/teey/frames/teey 5.png differ diff --git a/assets/teey/frames/teey 6.png b/assets/teey/frames/teey 6.png new file mode 100644 index 0000000..8997f49 Binary files /dev/null and b/assets/teey/frames/teey 6.png differ diff --git a/assets/teey/frames/teey 7.png b/assets/teey/frames/teey 7.png new file mode 100644 index 0000000..683d00a Binary files /dev/null and b/assets/teey/frames/teey 7.png differ diff --git a/assets/teey/frames/teey 8.png b/assets/teey/frames/teey 8.png new file mode 100644 index 0000000..b51c7e2 Binary files /dev/null and b/assets/teey/frames/teey 8.png differ diff --git a/assets/teey/frames/teey 9.png b/assets/teey/frames/teey 9.png new file mode 100644 index 0000000..e69440e Binary files /dev/null and b/assets/teey/frames/teey 9.png differ diff --git a/localization.config b/localization.config index 9b38c19..e668f20 100644 --- a/localization.config +++ b/localization.config @@ -59,6 +59,9 @@ You successfully voted for= [CommandShutdown] Stops kitty. `-s` or `safe` as an argument attempts to sync off the database before shutdown.= +[CommandTeey] +Teey yourself back into existance, or teey a friend with @!= + [CommandBetBeans] That's not a valid bet!= Please bet with at least 50 beans!= diff --git a/src/commands/CommandTeey.java b/src/commands/CommandTeey.java new file mode 100644 index 0000000..44e3d19 --- /dev/null +++ b/src/commands/CommandTeey.java @@ -0,0 +1,71 @@ +package commands; + +import java.io.File; +import java.io.IOException; +import javax.imageio.ImageIO; +import core.Command; +import core.Localizer; +import dataStructures.KittyChannel; +import dataStructures.KittyGuild; +import dataStructures.KittyRating; +import dataStructures.KittyRole; +import dataStructures.KittyUser; +import dataStructures.Response; +import dataStructures.UserInput; +import utils.ImageOverlayBuilder; +import utils.ImageUtils; + +public class CommandTeey extends Command +{ + // Required constructor + public CommandTeey(KittyRole level, KittyRating rating) { super(level, rating); } + + private static Long num = 0l; + + @Override + public String HelpText() { return Localizer.Stub("Teey yourself back into existance, or teey a friend with @!"); } + + // Called when the command is run! + @Override + public void OnRun(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input, Response res) + { + String name = null; + File teeyFile = null; + File teeyeeFile = null; + + synchronized(num) + { + name = "teey_" + num + ".gif"; + ++num; + } + + try + { + KittyUser person = null; + + if(input.mentions == null) + person = user; + else + person = input.mentions[0]; + + String yeeteeFilename = ImageUtils.DownloadFromURL(person.avatarID, ".png"); + if(yeeteeFilename == null) + return; + + teeyeeFile = new File(yeeteeFilename); + ImageOverlayBuilder builder = new ImageOverlayBuilder("assets/teey/frames/", "teey ", 24, 18); + builder.Overlay(ImageIO.read(teeyeeFile), name); + } + catch (IOException e) + { + e.printStackTrace(); + } + + teeyFile = new File (name); + res.CallFile(teeyFile, "gif"); + + // Thread cleanup... + ImageUtils.BlockingFileDelete(teeyFile); + ImageUtils.BlockingFileDelete(teeyeeFile); + } +} diff --git a/src/commands/CommandYeet.java b/src/commands/CommandYeet.java index 7b29add..b9f9195 100644 --- a/src/commands/CommandYeet.java +++ b/src/commands/CommandYeet.java @@ -1,24 +1,8 @@ package commands; -import java.awt.image.BufferedImage; -import java.awt.image.DataBufferByte; -import java.awt.image.RenderedImage; import java.io.File; 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.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.Localizer; import dataStructures.KittyChannel; @@ -28,11 +12,11 @@ import dataStructures.KittyRole; import dataStructures.KittyUser; import dataStructures.Response; import dataStructures.UserInput; +import utils.ImageOverlayBuilder; import utils.ImageUtils; public class CommandYeet extends Command { - // Required constructor public CommandYeet(KittyRole level, KittyRating rating) { super(level, rating); } @@ -60,20 +44,17 @@ public class CommandYeet extends Command KittyUser person = null; if(input.mentions == null) - { person = user; - } else - { person = input.mentions[0]; - } String yeeteeFilename = ImageUtils.DownloadFromURL(person.avatarID, ".png"); if(yeeteeFilename == null) return; 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) { @@ -87,251 +68,4 @@ public class CommandYeet extends Command ImageUtils.BlockingFileDelete(yeetFile); 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 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 IIOMetadataNode 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); - } - } } diff --git a/src/core/ObjectBuilderFactory.java b/src/core/ObjectBuilderFactory.java index 448e3cf..e568f71 100644 --- a/src/core/ObjectBuilderFactory.java +++ b/src/core/ObjectBuilderFactory.java @@ -300,6 +300,7 @@ public class ObjectBuilderFactory manager.Register("givebeans", new CommandGiveBeans(KittyRole.Mod, KittyRating.Safe)); manager.Register("rpg", new CommandRPG(KittyRole.Mod, KittyRating.Safe)); + manager.Register("teey", new CommandTeey(KittyRole.General, KittyRating.Safe)); manager.Register(new String[]{"perish", "thenperish"}, new CommandPerish(KittyRole.General, KittyRating.Safe)); manager.Register("yeet", new CommandYeet(KittyRole.General, KittyRating.Safe)); manager.Register("ping", new CommandPing(KittyRole.General, KittyRating.Safe)); diff --git a/src/utils/GlobalLog.java b/src/utils/GlobalLog.java index f350d2d..2cf28b9 100644 --- a/src/utils/GlobalLog.java +++ b/src/utils/GlobalLog.java @@ -13,7 +13,7 @@ public class GlobalLog } public static void Log(String msg) { Write(log, LogFilter.Debug, msg); } - public static void Warn(String msg) {Write(warn, LogFilter.Debug, msg); } + public static void Warn(String msg) { Write(warn, LogFilter.Debug, msg); } public static void Error(String msg) { Write(error, LogFilter.Debug, msg); } public static void Fatal(String msg) throws Exception { Write(fatal, LogFilter.Debug, msg); throw new Exception(msg); } diff --git a/src/utils/ImageOverlayBuilder.java b/src/utils/ImageOverlayBuilder.java new file mode 100644 index 0000000..2ad0546 --- /dev/null +++ b/src/utils/ImageOverlayBuilder.java @@ -0,0 +1,294 @@ +package utils; + +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferByte; +import java.awt.image.RenderedImage; +import java.io.File; +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.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; + +// 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) +// and then places the center of the overlay image on the specified location. Must be 32-bit PNGs with RGBA format. +public class ImageOverlayBuilder +{ + // 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 int DEFAULT_FPS = 18; + public static final String FRAME_FILE_EXTENSION = ".png"; + public static final String LOG_HEADER = "[Overlay] "; + + // Logging intermediate functions + private static void Verbose(String str) { if(VERBOSE) Log("[Verbose] " + str); } + private static void Log(String str) { GlobalLog.Log(LogFilter.Util, LOG_HEADER + str); } + private static void Warn(String str) { GlobalLog.Warn(LogFilter.Util, LOG_HEADER + str); } + private static void Error(String str) { GlobalLog.Error(LogFilter.Util, LOG_HEADER + str); } + + // Local variables + 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 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. + + // Required minimal constructor + 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.baseName = baseName; + this.frameCount = frameCount; + this.fps = fps; + } + + // Performs a per-frame overlay, catches IO errors as a note.) + public void Overlay(BufferedImage overlay, String outpath) + { + try + { + long start = Calendar.getInstance().getTimeInMillis(); + + ProcessOverlay(overlay, outpath); + + long end = Calendar.getInstance().getTimeInMillis(); + Log("Took " + (end - start) + "ms"); + } + catch (IOException e) + { + Error(e.getMessage()); + } + } + + // Processing the image frames to construct a gif. Relies on (0,255,0) pixel for image center specification. + private void ProcessOverlay(BufferedImage overlay, String outfileName) throws IOException + { + BufferedImage[] frames = new BufferedImage[frameCount]; + + for(int i = 0; i < frameCount; ++i) + { + BufferedImage out = CombineImages(basePath + baseName + i + FRAME_FILE_EXTENSION , overlay); + + if(out != null) + frames[i] = out; + else + Error("There was an issue with overlaying frame " + i); + } + + Verbose("Processed " + frameCount + " frames, writing..."); + ImageOutputStream output = new FileImageOutputStream(new File(outfileName)); + GifSequenceWriter writer = new GifSequenceWriter(output, frames[0].getType(), fps, true); + + for(int i = 0; i < frameCount; ++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. + private 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: ^ This is Elliot's original license + private 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 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 IIOMetadataNode 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); + } + } +} diff --git a/src/utils/LogFilter.java b/src/utils/LogFilter.java index eeb9d3a..22742fe 100644 --- a/src/utils/LogFilter.java +++ b/src/utils/LogFilter.java @@ -2,7 +2,8 @@ package utils; public enum LogFilter { - Debug(0), Command(1), Core(2), Database(4), Response(8), Network(16), Strings(32); //8, 16, 32... (flags, so we can | together later) + // Assign numbers as flags, so we can | ('or') them together as necessary + Debug(0), Command(1), Core(2), Util(4), Database(8), Response(16), Network(32), Strings(64); private final int value; private LogFilter(int value)