#include "ImageParser.h" #include "gifdec.h" #include "../PluginProcessor.h" ImageParser::ImageParser(OscirenderAudioProcessor& p, juce::String extension, juce::MemoryBlock image) : audioProcessor(p) { juce::TemporaryFile temp{".gif"}; juce::File file = temp.getFile(); { juce::FileOutputStream output(file); if (output.openedOk()) { output.write(image.getData(), image.getSize()); output.flush(); } } if (extension.equalsIgnoreCase(".gif")) { juce::String fileName = file.getFullPathName(); gd_GIF *gif = gd_open_gif(fileName.toRawUTF8()); if (gif != nullptr) { width = gif->width; height = gif->height; int frameSize = width * height; std::vector tempBuffer = std::vector(frameSize * 3); visited = std::vector(frameSize, false); int i = 0; while (gd_get_frame(gif) > 0) { gd_render_frame(gif, tempBuffer.data()); frames.emplace_back(std::vector(frameSize)); uint8_t *pixels = tempBuffer.data(); for (int j = 0; j < tempBuffer.size(); j += 3) { uint8_t avg = (pixels[j] + pixels[j + 1] + pixels[j + 2]) / 3; // value of 0 is reserved for transparent pixels frames[i][j / 3] = juce::jmax(1, (int) avg); } i++; } gd_close_gif(gif); } } else { juce::Image image = juce::ImageFileFormat::loadFrom(file); image.desaturate(); width = image.getWidth(); height = image.getHeight(); int frameSize = width * height; visited = std::vector(frameSize, false); frames.emplace_back(std::vector(frameSize)); for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { juce::Colour pixel = image.getPixelAt(x, y); int index = y * width + x; // RGB should be equal since we have desaturated int value = pixel.getRed(); // value of 0 is reserved for transparent pixels frames[0][index] = pixel.isTransparent() ? 0 : juce::jmax(1, value); } } } if (frames.size() == 0) { juce::MessageManager::callAsync([this] { juce::AlertWindow::showMessageBoxAsync(juce::AlertWindow::AlertIconType::WarningIcon, "Invalid GIF", "The image could not be loaded. Please try optimising the GIF with https://ezgif.com/optimize."); }); width = 1; height = 1; frames.emplace_back(std::vector(1)); } setFrame(0); } ImageParser::~ImageParser() {} void ImageParser::setFrame(int index) { // Ensure that the frame number is within the bounds of the number of frames // This weird modulo trick is to handle negative numbers frameIndex = (frames.size() + (index % frames.size())) % frames.size(); resetPosition(); std::fill(visited.begin(), visited.end(), false); } bool ImageParser::isOverThreshold(double pixel, double thresholdPow) { float threshold = std::pow(pixel, thresholdPow); return pixel > 0.2 && rng.nextFloat() < threshold; } void ImageParser::resetPosition() { currentX = width > 0 ? rng.nextInt(width) : 0; currentY = height > 0 ? rng.nextInt(height) : 0; } float ImageParser::getPixelValue(int x, int y, bool invert) { int index = (height - y - 1) * width + x; float pixel = frames[frameIndex][index] / (float) std::numeric_limits::max(); // never traverse transparent pixels if (invert && pixel > 0) { pixel = 1 - pixel; } return pixel; } void ImageParser::findWhite(double thresholdPow, bool invert) { for (int i = 0; i < 100; i++) { resetPosition(); if (isOverThreshold(getPixelValue(currentX, currentY, invert), thresholdPow)) { break; } } } int ImageParser::jumpFrequency() { return audioProcessor.currentSampleRate * 0.005; } void ImageParser::findNearestNeighbour(int searchRadius, float thresholdPow, int stride, bool invert) { int spiralSteps[4][2] = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}}; int maxSteps = 2 * searchRadius; // Maximum steps outwards in the spiral int x = currentX; int y = currentY; int dir = rng.nextInt(4); for (int len = 1; len <= maxSteps; len++) { // Length of spiral arm for (int i = 0 ; i < 2; i++) { // Repeat twice for each arm length for (int step = 0 ; step < len; step++) { // Steps in the current direction x += stride * spiralSteps[dir][0]; y += stride * spiralSteps[dir][1]; if (x < 0 || x >= width || y < 0 || y >= height) break; float pixel = getPixelValue(x, y, invert); int index = (height - y - 1) * width + x; if (isOverThreshold(pixel, thresholdPow) && !visited[index]) { visited[index] = true; currentX = x; currentY = y; return; } } dir = (dir + 1) % 4; // Change direction after finishing one leg of the spiral } } findWhite(thresholdPow, invert); } OsciPoint ImageParser::getSample() { if (count % jumpFrequency() == 0) { resetPosition(); } if (count % 10 * jumpFrequency() == 0) { std::fill(visited.begin(), visited.end(), false); } float thresholdPow = audioProcessor.imageThreshold->getActualValue() * 10 + 1; findNearestNeighbour(10, thresholdPow, audioProcessor.imageStride->getActualValue(), audioProcessor.invertImage->getValue()); float maxDim = juce::jmax(width, height); count++; float widthDiff = (maxDim - width) / 2; float heightDiff = (maxDim - height) / 2; return OsciPoint(2 * (currentX + widthDiff) / maxDim - 1, 2 * (currentY + heightDiff) / maxDim - 1); }