Several improvements to ImageParser, support for all image types, show only relevant controls in FrameSettingsComponent

pull/242/head
James H Ball 2024-06-01 21:50:56 +01:00 zatwierdzone przez James H Ball
rodzic ba0d55a1f6
commit 9726317928
10 zmienionych plików z 150 dodań i 72 usunięć

Wyświetl plik

@ -58,40 +58,47 @@ FrameSettingsComponent::FrameSettingsComponent(OscirenderAudioProcessor& p, Osci
audioProcessor.invertImage->addListener(this);
}
FrameSettingsComponent::~FrameSettingsComponent() {
audioProcessor.invertImage->removeListener(this);
audioProcessor.animationOffset->removeListener(this);
audioProcessor.animationRate->removeListener(this);
audioProcessor.animationSyncBPM->removeListener(this);
audioProcessor.animateFrames->removeListener(this);
FrameSettingsComponent::~FrameSettingsComponent() {
audioProcessor.invertImage->removeListener(this);
audioProcessor.animationOffset->removeListener(this);
audioProcessor.animationRate->removeListener(this);
audioProcessor.animationSyncBPM->removeListener(this);
audioProcessor.animateFrames->removeListener(this);
}
void FrameSettingsComponent::resized() {
auto area = getLocalBounds().withTrimmedTop(20).reduced(20);
auto firstColumn = area.removeFromLeft(220);
double rowHeight = 20;
double rowSpace = 10;
auto animateBounds = firstColumn.removeFromTop(rowHeight);
animate.setBounds(animateBounds.removeFromLeft(100));
sync.setBounds(animateBounds.removeFromLeft(100));
firstColumn.removeFromTop(rowSpace);
double rowHeight = 20;
if (animated) {
double rowSpace = 10;
auto firstColumn = area.removeFromLeft(220);
auto animateBounds = firstColumn.removeFromTop(rowHeight);
animate.setBounds(animateBounds.removeFromLeft(100));
sync.setBounds(animateBounds.removeFromLeft(100));
firstColumn.removeFromTop(rowSpace);
animateBounds = firstColumn.removeFromTop(rowHeight);
rateLabel.setBounds(animateBounds.removeFromLeft(140));
rateBox.setBounds(animateBounds.removeFromLeft(60));
firstColumn.removeFromTop(rowSpace);
animateBounds = firstColumn.removeFromTop(rowHeight);
rateLabel.setBounds(animateBounds.removeFromLeft(140));
rateBox.setBounds(animateBounds.removeFromLeft(60));
firstColumn.removeFromTop(rowSpace);
animateBounds = firstColumn.removeFromTop(rowHeight);
offsetLabel.setBounds(animateBounds.removeFromLeft(140));
offsetBox.setBounds(animateBounds.removeFromLeft(60));
animateBounds = firstColumn.removeFromTop(rowHeight);
offsetLabel.setBounds(animateBounds.removeFromLeft(140));
offsetBox.setBounds(animateBounds.removeFromLeft(60));
}
auto secondColumn = area;
auto invertBounds = secondColumn.removeFromTop(rowHeight);
invertImage.setBounds(invertBounds.removeFromLeft(100));
secondColumn.removeFromTop(rowSpace);
threshold.setBounds(secondColumn.removeFromTop(rowHeight));
stride.setBounds(secondColumn.removeFromTop(rowHeight));
if (image) {
auto secondColumn = area;
auto invertBounds = secondColumn.removeFromTop(rowHeight);
invertImage.setBounds(invertBounds.removeFromLeft(100));
secondColumn.removeFromTop(5);
rowHeight = 30;
threshold.setBounds(secondColumn.removeFromTop(rowHeight));
stride.setBounds(secondColumn.removeFromTop(rowHeight));
}
}
void FrameSettingsComponent::update() {
@ -118,3 +125,20 @@ void FrameSettingsComponent::parameterGestureChanged(int parameterIndex, bool ge
void FrameSettingsComponent::handleAsyncUpdate() {
update();
}
void FrameSettingsComponent::setAnimated(bool animated) {
this->animated = animated;
animate.setVisible(animated);
sync.setVisible(animated);
rateBox.setVisible(animated);
offsetBox.setVisible(animated);
rateLabel.setVisible(animated);
offsetLabel.setVisible(animated);
}
void FrameSettingsComponent::setImage(bool image) {
this->image = image;
invertImage.setVisible(image);
threshold.setVisible(image);
stride.setVisible(image);
}

Wyświetl plik

@ -16,9 +16,14 @@ public:
void parameterGestureChanged(int parameterIndex, bool gestureIsStarting) override;
void handleAsyncUpdate() override;
void update();
void setAnimated(bool animated);
void setImage(bool image);
private:
OscirenderAudioProcessor& audioProcessor;
OscirenderAudioProcessorEditor& pluginEditor;
bool animated = true;
bool image = true;
juce::ToggleButton animate{"Animate"};
juce::ToggleButton sync{"BPM Sync"};
@ -32,4 +37,4 @@ private:
EffectComponent stride{ audioProcessor, *audioProcessor.imageStride };
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FrameSettingsComponent)
};
};

Wyświetl plik

@ -10,7 +10,7 @@ MainComponent::MainComponent(OscirenderAudioProcessor& p, OscirenderAudioProcess
fileButton.setButtonText("Choose File(s)");
fileButton.onClick = [this] {
chooser = std::make_unique<juce::FileChooser>("Open", juce::File::getSpecialLocation(juce::File::userHomeDirectory), "*.obj;*.svg;*.lua;*.txt;*.gpla;*.gif");
chooser = std::make_unique<juce::FileChooser>("Open", juce::File::getSpecialLocation(juce::File::userHomeDirectory), "*.obj;*.svg;*.lua;*.txt;*.gpla;*.gif;*.png;*.jpg;*.jpeg");
auto flags = juce::FileBrowserComponent::openMode | juce::FileBrowserComponent::canSelectMultipleItems |
juce::FileBrowserComponent::canSelectFiles;

Wyświetl plik

@ -805,7 +805,8 @@ void OscirenderAudioProcessor::getStateInformation(juce::MemoryBlock& destData)
for (int i = 0; i < fileBlocks.size(); i++) {
auto fileXml = filesXml->createNewChildElement("file");
fileXml->setAttribute("name", fileNames[i]);
fileXml->addTextElement(fileBlocks[i]->toBase64Encoding());
auto base64 = fileBlocks[i]->toBase64Encoding();
fileXml->addTextElement(base64);
}
xml->setAttribute("currentFile", currentFile);
@ -911,7 +912,7 @@ void OscirenderAudioProcessor::setStateInformation(const void* data, int sizeInB
if (lessThanVersion(version, "2.2.0")) {
// Older versions of osci-render opened files in a silly way
auto stream = juce::MemoryOutputStream();
juce::Base64::convertFromBase64(stream, fileXml->getAllSubText());
juce::Base64::convertFromBase64(stream, text);
fileBlock = std::make_shared<juce::MemoryBlock>(stream.getData(), stream.getDataSize());
} else {
fileBlock = std::make_shared<juce::MemoryBlock>();

Wyświetl plik

@ -306,20 +306,20 @@ private:
double valueFromLegacy(double value, const juce::String& id);
void changeSound(ShapeSound::Ptr sound);
void parseVersion(int result[4], const juce::String& input) {
void parseVersion(int result[3], const juce::String& input) {
std::istringstream parser(input.toStdString());
parser >> result[0];
for (int idx = 1; idx < 4; idx++) {
for (int idx = 1; idx < 3; idx++) {
parser.get(); //Skip period
parser >> result[idx];
}
}
bool lessThanVersion(const juce::String& a, const juce::String& b) {
int parsedA[4], parsedB[4];
int parsedA[3], parsedB[3];
parseVersion(parsedA, a);
parseVersion(parsedB, b);
return std::lexicographical_compare(parsedA, parsedA + 4, parsedB, parsedB + 4);
return std::lexicographical_compare(parsedA, parsedA + 3, parsedB, parsedB + 3);
}
const double MIN_LENGTH_INCREMENT = 0.000001;

Wyświetl plik

@ -9,7 +9,7 @@ SettingsComponent::SettingsComponent(OscirenderAudioProcessor& p, OscirenderAudi
addAndMakeVisible(mainResizerBar);
addAndMakeVisible(midi);
addChildComponent(txt);
addChildComponent(gpla);
addChildComponent(frame);
midiLayout.setItemLayout(0, -0.1, -1.0, -1.0);
midiLayout.setItemLayout(1, pluginEditor.RESIZER_BAR_SIZE, pluginEditor.RESIZER_BAR_SIZE, pluginEditor.RESIZER_BAR_SIZE);
@ -47,14 +47,14 @@ void SettingsComponent::resized() {
if (txt.isVisible()) {
effectSettings = &txt;
} else if (gpla.isVisible()) {
effectSettings = &gpla;
} else if (frame.isVisible()) {
effectSettings = &frame;
}
auto dummyBounds = dummy.getBounds();
if (effectSettings != nullptr) {
effectSettings->setBounds(dummyBounds.removeFromBottom(140));
effectSettings->setBounds(dummyBounds.removeFromBottom(150));
dummyBounds.removeFromBottom(pluginEditor.RESIZER_BAR_SIZE);
}
@ -66,14 +66,17 @@ void SettingsComponent::resized() {
void SettingsComponent::fileUpdated(juce::String fileName) {
juce::String extension = fileName.fromLastOccurrenceOf(".", true, false);
txt.setVisible(false);
gpla.setVisible(false);
frame.setVisible(false);
bool isImage = extension == ".gif" || extension == ".png" || extension == ".jpg" || extension == ".jpeg";
if (fileName.isEmpty() || audioProcessor.objectServerRendering) {
// do nothing
} else if (extension == ".txt") {
txt.setVisible(true);
}
else if (extension == ".gpla" || extension == ".gif") {
gpla.setVisible(true);
} else if (extension == ".gpla" || isImage) {
frame.setVisible(true);
frame.setAnimated(extension == ".gpla" || extension == ".gif");
frame.setImage(isImage);
frame.resized();
}
main.updateFileLabel();
resized();
@ -81,7 +84,7 @@ void SettingsComponent::fileUpdated(juce::String fileName) {
void SettingsComponent::update() {
txt.update();
gpla.update();
frame.update();
}
void SettingsComponent::mouseMove(const juce::MouseEvent& event) {

Wyświetl plik

@ -28,7 +28,7 @@ private:
MainComponent main{audioProcessor, pluginEditor};
PerspectiveComponent perspective{audioProcessor, pluginEditor};
TxtComponent txt{audioProcessor, pluginEditor};
FrameSettingsComponent gpla{ audioProcessor, pluginEditor };
FrameSettingsComponent frame{ audioProcessor, pluginEditor };
EffectsComponent effects{audioProcessor, pluginEditor};
MidiComponent midi{audioProcessor, pluginEditor};
@ -42,4 +42,4 @@ private:
double prefSizes[1] = { 300 };
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SettingsComponent)
};
};

Wyświetl plik

@ -3,19 +3,19 @@
#include "../PluginProcessor.h"
ImageParser::ImageParser(OscirenderAudioProcessor& p, juce::String extension, juce::MemoryBlock image) : audioProcessor(p) {
if (extension.equalsIgnoreCase(".gif")) {
juce::TemporaryFile temp{".gif"};
juce::File file = temp.getFile();
juce::TemporaryFile temp{".gif"};
juce::File file = temp.getFile();
{
juce::FileOutputStream output(file);
{
juce::FileOutputStream output(file);
if (output.openedOk()) {
output.write(image.getData(), image.getSize());
output.flush();
}
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());
@ -43,6 +43,25 @@ ImageParser::ImageParser(OscirenderAudioProcessor& p, juce::String extension, ju
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<bool>(frameSize, false);
frames.emplace_back(std::vector<uint8_t>(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
frames[0][index] = pixel.getRed();
}
}
}
setFrame(0);
@ -58,9 +77,29 @@ void ImageParser::setFrame(int index) {
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 = rng.nextInt(width);
currentY = rng.nextInt(height);
currentX = width > 0 ? rng.nextInt(width) : 0;
currentY = height > 0 ? rng.nextInt(height) : 0;
}
float ImageParser::getPixelValue(int x, int y) {
int index = (height - y - 1) * width + x;
float pixel = frames[frameIndex][index] / (float) std::numeric_limits<uint8_t>::max();
return pixel;
}
void ImageParser::findWhite(double thresholdPow) {
for (int i = 0; i < 100; i++) {
resetPosition();
if (isOverThreshold(getPixelValue(currentX, currentY), thresholdPow)) {
break;
}
}
}
int ImageParser::jumpFrequency() {
@ -74,8 +113,6 @@ void ImageParser::findNearestNeighbour(int searchRadius, float thresholdPow, int
int y = currentY;
int dir = rng.nextInt(4);
float maxValue = std::numeric_limits<uint8_t>::max();
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
@ -83,15 +120,14 @@ void ImageParser::findNearestNeighbour(int searchRadius, float thresholdPow, int
y += stride * spiralSteps[dir][1];
if (x < 0 || x >= width || y < 0 || y >= height) break;
int index = (height - y - 1) * width + x;
float pixel = frames[frameIndex][index] / maxValue;
float pixel = getPixelValue(x, y);
if (invert) {
pixel = 1 - pixel;
}
float threshold = std::pow(pixel, thresholdPow);
if (pixel > 0.2 && rng.nextFloat() < threshold && !visited[index]) {
int index = (height - y - 1) * width + x;
if (isOverThreshold(pixel, thresholdPow) && !visited[index]) {
visited[index] = true;
currentX = x;
currentY = y;
@ -103,18 +139,24 @@ void ImageParser::findNearestNeighbour(int searchRadius, float thresholdPow, int
}
}
resetPosition();
findWhite(thresholdPow);
}
Point ImageParser::getSample() {
if (count % jumpFrequency() == 0) {
resetPosition();
}
if (count % 10 * jumpFrequency() == 0) {
std::fill(visited.begin(), visited.end(), false);
}
float thresholdPow = audioProcessor.imageThreshold->getValue() * 10 + 1;
findNearestNeighbour(50, thresholdPow, audioProcessor.imageStride->getValue(), audioProcessor.invertImage->getValue());
findNearestNeighbour(10, thresholdPow, audioProcessor.imageStride->getValue(), audioProcessor.invertImage->getValue());
float maxDim = juce::jmax(width, height);
count++;
return Point(2 * currentX / maxDim - 1, 2 * currentY / maxDim - 1);
float widthDiff = (maxDim - width) / 2;
float heightDiff = (maxDim - height) / 2;
return Point(2 * (currentX + widthDiff) / maxDim - 1, 2 * (currentY + heightDiff) / maxDim - 1);
}

Wyświetl plik

@ -17,6 +17,9 @@ public:
private:
void findNearestNeighbour(int searchRadius, float thresholdPow, int stride, bool invert);
void resetPosition();
float getPixelValue(int x, int y);
void findWhite(double thresholdPow);
bool isOverThreshold(double pixel, double thresholdValue);
int jumpFrequency();
OscirenderAudioProcessor& audioProcessor;
@ -27,4 +30,4 @@ private:
int currentX, currentY;
int width, height;
int count = 0;
};
};

Wyświetl plik

@ -29,13 +29,13 @@ void FileParser::parse(juce::String fileId, juce::String extension, std::unique_
lua = std::make_shared<LuaParser>(fileId, stream->readEntireStreamAsString(), errorCallback, fallbackLuaScript);
} else if (extension == ".gpla") {
gpla = std::make_shared<LineArtParser>(stream->readEntireStreamAsString());
} else if (extension == ".gif") {
} else if (extension == ".gif" || extension == ".png" || extension == ".jpg" || extension == ".jpeg") {
juce::MemoryBlock buffer{};
int bytesRead = stream->readIntoMemoryBlock(buffer);
img = std::make_shared<ImageParser>(audioProcessor, extension, buffer);
}
isAnimatable = gpla != nullptr || img != nullptr;
isAnimatable = gpla != nullptr || (img != nullptr && extension == ".gif");
sampleSource = lua != nullptr || img != nullptr;
}
@ -121,4 +121,4 @@ std::shared_ptr<LuaParser> FileParser::getLua() {
std::shared_ptr<ImageParser> FileParser::getImg() {
return img;
}
}