kopia lustrzana https://github.com/jameshball/osci-render
Several improvements to ImageParser, support for all image types, show only relevant controls in FrameSettingsComponent
rodzic
ba0d55a1f6
commit
9726317928
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue