2024-05-11 22:10:54 +00:00
# include "ImageParser.h"
# include "gifdec.h"
# include "../PluginProcessor.h"
ImageParser : : ImageParser ( OscirenderAudioProcessor & p , juce : : String extension , juce : : MemoryBlock image ) : audioProcessor ( p ) {
2024-06-01 20:50:56 +00:00
juce : : TemporaryFile temp { " .gif " } ;
juce : : File file = temp . getFile ( ) ;
2024-05-11 22:10:54 +00:00
2024-06-01 20:50:56 +00:00
{
juce : : FileOutputStream output ( file ) ;
2024-05-11 22:10:54 +00:00
2024-06-01 20:50:56 +00:00
if ( output . openedOk ( ) ) {
output . write ( image . getData ( ) , image . getSize ( ) ) ;
output . flush ( ) ;
2024-05-11 22:10:54 +00:00
}
2024-06-01 20:50:56 +00:00
}
if ( extension . equalsIgnoreCase ( " .gif " ) ) {
2024-05-11 22:10:54 +00:00
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 < uint8_t > tempBuffer = std : : vector < uint8_t > ( frameSize * 3 ) ;
visited = std : : vector < bool > ( frameSize , false ) ;
int i = 0 ;
while ( gd_get_frame ( gif ) > 0 ) {
gd_render_frame ( gif , tempBuffer . data ( ) ) ;
frames . emplace_back ( std : : vector < uint8_t > ( 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 ;
2024-06-02 16:15:04 +00:00
// value of 0 is reserved for transparent pixels
frames [ i ] [ j / 3 ] = juce : : jmax ( 1 , ( int ) avg ) ;
2024-05-11 22:10:54 +00:00
}
i + + ;
}
gd_close_gif ( gif ) ;
}
2024-06-01 20:50:56 +00:00
} 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
2024-06-02 16:15:04 +00:00
int value = pixel . getRed ( ) ;
// value of 0 is reserved for transparent pixels
frames [ 0 ] [ index ] = pixel . isTransparent ( ) ? 0 : juce : : jmax ( 1 , value ) ;
2024-06-01 20:50:56 +00:00
}
}
2024-05-11 22:10:54 +00:00
}
2025-01-14 20:04:46 +00:00
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 < uint8_t > ( 1 ) ) ;
}
2024-05-11 22:10:54 +00:00
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 ) ;
}
2024-06-01 20:50:56 +00:00
bool ImageParser : : isOverThreshold ( double pixel , double thresholdPow ) {
float threshold = std : : pow ( pixel , thresholdPow ) ;
return pixel > 0.2 & & rng . nextFloat ( ) < threshold ;
}
2024-05-11 22:10:54 +00:00
void ImageParser : : resetPosition ( ) {
2024-06-01 20:50:56 +00:00
currentX = width > 0 ? rng . nextInt ( width ) : 0 ;
currentY = height > 0 ? rng . nextInt ( height ) : 0 ;
}
2024-06-02 16:15:04 +00:00
float ImageParser : : getPixelValue ( int x , int y , bool invert ) {
2024-06-01 20:50:56 +00:00
int index = ( height - y - 1 ) * width + x ;
2025-02-01 18:14:16 +00:00
if ( index < 0 | | index > = frames [ frameIndex ] . size ( ) ) {
return 0 ;
}
2024-06-01 20:50:56 +00:00
float pixel = frames [ frameIndex ] [ index ] / ( float ) std : : numeric_limits < uint8_t > : : max ( ) ;
2024-06-02 16:15:04 +00:00
// never traverse transparent pixels
if ( invert & & pixel > 0 ) {
pixel = 1 - pixel ;
}
2024-06-01 20:50:56 +00:00
return pixel ;
}
2024-06-02 16:15:04 +00:00
void ImageParser : : findWhite ( double thresholdPow , bool invert ) {
2024-06-01 20:50:56 +00:00
for ( int i = 0 ; i < 100 ; i + + ) {
resetPosition ( ) ;
2024-06-02 16:15:04 +00:00
if ( isOverThreshold ( getPixelValue ( currentX , currentY , invert ) , thresholdPow ) ) {
2024-06-01 20:50:56 +00:00
break ;
}
}
2024-05-11 22:10:54 +00:00
}
2024-05-12 20:12:35 +00:00
int ImageParser : : jumpFrequency ( ) {
return audioProcessor . currentSampleRate * 0.005 ;
}
2024-05-11 22:10:54 +00:00
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 ;
2024-06-01 20:50:56 +00:00
2024-06-02 16:15:04 +00:00
float pixel = getPixelValue ( x , y , invert ) ;
2024-05-11 22:10:54 +00:00
2024-06-01 20:50:56 +00:00
int index = ( height - y - 1 ) * width + x ;
if ( isOverThreshold ( pixel , thresholdPow ) & & ! visited [ index ] ) {
2024-05-11 22:10:54 +00:00
visited [ index ] = true ;
currentX = x ;
currentY = y ;
return ;
}
}
dir = ( dir + 1 ) % 4 ; // Change direction after finishing one leg of the spiral
}
}
2024-06-02 16:15:04 +00:00
findWhite ( thresholdPow , invert ) ;
2024-05-11 22:10:54 +00:00
}
2024-10-23 11:44:31 +00:00
OsciPoint ImageParser : : getSample ( ) {
2024-05-12 20:12:35 +00:00
if ( count % jumpFrequency ( ) = = 0 ) {
2024-05-11 22:10:54 +00:00
resetPosition ( ) ;
}
2024-06-01 20:50:56 +00:00
if ( count % 10 * jumpFrequency ( ) = = 0 ) {
std : : fill ( visited . begin ( ) , visited . end ( ) , false ) ;
}
2024-05-11 22:10:54 +00:00
2024-06-02 14:01:39 +00:00
float thresholdPow = audioProcessor . imageThreshold - > getActualValue ( ) * 10 + 1 ;
2024-05-11 22:10:54 +00:00
2024-06-02 14:01:39 +00:00
findNearestNeighbour ( 10 , thresholdPow , audioProcessor . imageStride - > getActualValue ( ) , audioProcessor . invertImage - > getValue ( ) ) ;
2024-05-11 22:10:54 +00:00
float maxDim = juce : : jmax ( width , height ) ;
count + + ;
2024-06-01 20:50:56 +00:00
float widthDiff = ( maxDim - width ) / 2 ;
float heightDiff = ( maxDim - height ) / 2 ;
2024-10-23 11:44:31 +00:00
return OsciPoint ( 2 * ( currentX + widthDiff ) / maxDim - 1 , 2 * ( currentY + heightDiff ) / maxDim - 1 ) ;
2024-05-11 22:10:54 +00:00
}