captFuture_HexaBotArt/imagetools.pde
void draw_preview_image() {
    image(img, 0, 0, width / 2, height / 2); 
}


///////////////////////////////////////////////////////////////////////////////////////////////////////
void image_threshold() {
    code_comment("Thresholed");
    img.filter(THRESHOLD);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////
void image_desaturate() {
    code_comment("image_desaturate");
    img.filter(GRAY);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////
void image_invert() {
    code_comment("image_invert");
    img.filter(INVERT);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////
void image_posterize(int amount) {
    code_comment("image_posterize");
    img.filter(POSTERIZE, amount);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////
void image_blur(int amount) {
    code_comment("image_blur");
    img.filter(BLUR, amount);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////
void image_erode() {
    code_comment("image_erode");
    img.filter(ERODE);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////
void image_dilate() {
    code_comment("image_dilate");
    img.filter(DILATE);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////
void image_rotate() {
    //image[y][x]                                     // assuming this is the original orientation
    //image[x][original_width - y]                    // rotated 90 degrees ccw
    //image[original_height - x][y]                   // 90 degrees cw
    //image[original_height - y][original_width - x]  // 180 degrees
    
    if(img.width > img.height) {
        PImage img2 = createImage(img.height, img.width, RGB);
        img.loadPixels();
        for (int x = 1; x < img.width; x++) {
            for (int y = 1; y < img.height; y++) {
                int loc1= x + y * img.width;
                int loc2= y + (img.width - x) * img2.width;
                img2.pixels[loc2] = img.pixels[loc1];
        }
        }
        img = img2;
        updatePixels();
        code_comment("image_rotate: rotated 90 degrees to fit machines sweet spot");
} else {
        code_comment("image_rotate: no rotation necessary");
}
}

///////////////////////////////////////////////////////////////////////////////////////////////////////
void lighten_one_pixel(int adjustbrightness, int x, int y) {
    int loc = (y) * img.width + x;
    float r = brightness(img.pixels[loc]);
    //r += adjustbrightness;
    r += adjustbrightness + random(0, 0.01);
    r = constrain(r,0,255);
    color c = color(r);
    img.pixels[loc] = c;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////
void image_scale(int new_width) {
    if(img.width != new_width) {
        code_comment("image_scale, old size: " + img.width + " by " + img.height + "     ratio: " + (float)img.width / (float)img.height);
        img.resize(new_width, 0);
        code_comment("image_scale, new size: " + img.width + " by " + img.height + "     ratio: " + (float)img.width / (float)img.height);
}
}

///////////////////////////////////////////////////////////////////////////////////////////////////////
float avg_imgage_brightness() {
    float b = 0.0;
    
    for (int p = 0; p < img.width * img.height; p++) {
        b +=brightness(img.pixels[p]);
}
    
    return(b / (img.width * img.height));
}

///////////////////////////////////////////////////////////////////////////////////////////////////////
void image_crop() {
    //This will center crop to the desired image size image_size_x and image_size_y
    
    PImage img2;
    float desired_ratio = image_size_x / image_size_y;
    float current_ratio = (float)img.width / (float)img.height;
    
    code_comment("image_crop desired ratio of " + desired_ratio);
    code_comment("image_crop old size: " + img.width + " by " + img.height + "     ratio: " + current_ratio);
    
    if(current_ratio < desired_ratio) {
        int desired_x = img.width;
        int desired_y = int(img.width / desired_ratio);
        int half_y = (img.height - desired_y) / 2;
        img2= createImage(desired_x, desired_y, RGB);
        img2.copy(img, 0, half_y, desired_x, desired_y, 0, 0, desired_x, desired_y);
} else {
        int desired_x = int(img.height * desired_ratio);
        int desired_y = img.height;
        int half_x = (img.width - desired_x) / 2;
        img2= createImage(desired_x, desired_y, RGB);
        img2.copy(img, half_x, 0, desired_x, desired_y, 0, 0, desired_x, desired_y);
}
    
    img = img2;
    code_comment("image_crop new size: " + img.width + " by " + img.height + "     ratio: " + (float)img.width / (float)img.height);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////
void image_border(String fname, int shrink, int blur) {
    //A quick and dirty way of softening the edges of your drawing.
    //Look in the borders directory for some examples.
    //Ideally, the border will have similar dimensions as the image to be drawn.
    //For far more control, just edit your input image directly.
    //Most of the examples are pretty heavy handed so you can "shrink" them a few pixels as desired.
    //It does not matter if you use a transparant background or just white.  JPEG or PNG, it's all good.
    //
    //fname:   Name of border file.
    //shrink:  Number of pixels to pull the border away, 0 for no change. 
    //blur:    Guassian blur the border, 0 for no blur, 10+ for a lot.
    
    //PImage border = createImage(img.width+(shrink*2), img.height+(shrink*2), RGB);
    PImage temp_border = loadImage("border/" + fname);
    temp_border.resize(img.width, img.height);
    temp_border.filter(GRAY);
    temp_border.filter(INVERT);
    temp_border.filter(BLUR, blur);
    
    //border.copy(temp_border, 0, 0, temp_border.width, temp_border.height, 0, 0, border.width, border.height);
    img.blend(temp_border, shrink, shrink, img.width, img.height,  0, 0, img.width, img.height, ADD); 
    code_comment("image_border: " + fname + "   " + shrink + "   " + blur);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////
void image_unsharpen(PImage img, int amount) {
    //Source:  https://www.taylorpetrick.com/blog/post/convolution-part3
    //Subtle unsharp matrix
    float[][] matrix = { { - 0.00391, -0.01563, -0.02344, -0.01563, -0.00391 } ,
        { - 0.01563, -0.06250, -0.09375, -0.06250, -0.01563 } ,
        { - 0.02344, -0.09375,  1.85980, -0.09375, -0.02344 } ,
        { - 0.01563, -0.06250, -0.09375, -0.06250, -0.01563 } ,
        { - 0.00391, -0.01563, -0.02344, -0.01563, -0.00391 } };
    
    
    //print_matrix(matrix);
    matrix = scale_matrix(matrix, amount);
    //print_matrix(matrix);
    matrix = normalize_matrix(matrix);
    //print_matrix(matrix);
    
    image_convolution(img, matrix, 1.0, 0.0);
    code_comment("image_unsharpen: " + amount);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////
void image_blurr(PImage img) {
    //Basic blur matrix
    
    float[][] matrix = { { 1, 1, 1 } ,
        { 1, 1, 1 } ,
        { 1, 1, 1 } }; 
    
    matrix = normalize_matrix(matrix);
    image_convolution(img, matrix, 1, 0);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////
void image_sharpen(PImage img) {
    //Simple sharpen matrix
    
    float[][] matrix = { {  0, -1,  0 } ,
        { - 1,  5, -1 } ,
        {  0, -1,  0 } }; 
    
    //print_matrix(matrix);
    image_convolution(img, matrix, 1, 0);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////
void image_emboss(PImage img) {
    float[][] matrix = { { - 2, -1,  0 } ,
        { - 1,  1,  1 } ,
        {  0,  1,  2 } }; 
    
    image_convolution(img, matrix, 1, 0);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////
void image_edge_detect(PImage img) {
    //Edge detect
    float[][] matrix = { {  0,  1,  0 } ,
        {  1, -4,  1 } ,
        {  0,  1,  0 } }; 
    
    image_convolution(img, matrix, 1, 0);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////
void image_motion_blur(PImage img) {
    //Motion Blur
    //http://lodev.org/cgtutor/filtering.html
    
    float[][] matrix = { {  1, 0, 0, 0, 0, 0, 0, 0, 0 } ,
        {  0, 1, 0, 0, 0, 0, 0, 0, 0 } ,
        {  0, 0, 1, 0, 0, 0, 0, 0, 0 } ,
        {  0, 0, 0, 1, 0, 0, 0, 0, 0 } ,
        {  0, 0, 0, 0, 1, 0, 0, 0, 0 } ,
        {  0, 0, 0, 0, 0, 1, 0, 0, 0 } ,
        {  0, 0, 0, 0, 0, 0, 1, 0, 0 } ,
        {  0, 0, 0, 0, 0, 0, 0, 1, 0 } ,
        {  0, 0, 0, 0, 0, 0, 0, 0, 1 } };
    
    matrix = normalize_matrix(matrix);
    image_convolution(img, matrix, 1, 0);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////
void image_outline(PImage img) {
    //Outline (5x5)
    //https://www.jmicrovision.com/help/v125/tools/classicfilterop.htm
    
    float[][] matrix = { { 1,  1,   1,  1,  1 } ,
        { 1,  0,   0,  0,  1 } ,
        { 1,  0, -16,  0,  1 } ,
        { 1,  0,   0,  0,  1 } ,
        { 1,  1,   1,  1,  1 } };
    
    //matrix = normalize_matrix(matrix);
    image_convolution(img, matrix, 1, 0);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////
void image_sobel(PImage img, float factor, float bias) {
    
    //Looks like some kind of inverting edge detection
    //float[][] matrix = { { -1, -1, -1 },
    //{ -1,  8, -1 },
    //{ -1, -1, -1 } }; 
    
    //float[][] matrix = { {  1,  2,   0,  -2,  -1 },
    //{  4,  8,   0,  -8,  -4 },
    //{  6, 12,   0, -12,  -6 },
    //{  4,  8,   0,  -8,  -4 },
    //{  1,  2,   0,  -2,  -1 } };
    
    //Sobel 3x3 X
    float[][] matrixX = { { - 1,  0,  1 } ,
        { - 2,  0,  2 } ,
        { - 1,  0,  1 } }; 
    
    //Sobel 3x3 Y
    float[][] matrixY = { { - 1, -2, -1 } ,
        {  0,  0,  0 } ,
        {  1,  2,  1 } }; 
    
    image_convolution(img, matrixX, factor, bias);
    image_convolution(img, matrixY, factor, bias);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////
void image_convolution(PImage img, float[][] matrix, float factor, float bias) {
    //What about edge pixels?  Ignoring (maxrixsize-1)/2 pixels on the edges?
    
    int n = matrix.length;      // matrix rows
    int m = matrix[0].length;   // matrix columns
    
    //print_matrix(matrix);
    
    PImage simg = createImage(img.width, img.height, RGB);
    simg.copy(img, 0, 0, img.width, img.height, 0, 0, simg.width, simg.height);
    int matrixsize = matrix.length;
    
    for (int x = 0; x < simg.width; x++) {
        for (int y = 0; y < simg.height; y++) {
            color c = convolution(x, y, matrix, matrixsize, simg, factor, bias);
            int loc = x + y * simg.width;
            img.pixels[loc] = c;
        }
}
    updatePixels();
}


///////////////////////////////////////////////////////////////////////////////////////////////////////
// Source:  https://py.processing.org/tutorials/pixels/
// By: Daniel Shiffman
// Factor & bias added by SCC

color convolution(int x, int y, float[][] matrix, int matrixsize, PImage img, float factor, float bias) {
    float rtotal = 0.0;
    float gtotal = 0.0;
    float btotal = 0.0;
    int offset = matrixsize / 2;
    
    //Loop through convolution matrix
    for (int i = 0; i < matrixsize; i++) {
        for (int j = 0; j < matrixsize; j++) {
           // What pixel are we testing
            int xloc = x + i - offset;
            int yloc = y + j - offset;
            int loc = xloc + img.width * yloc;
           // Make sure we have not walked off the edge of the pixel array
            loc = constrain(loc,0,img.pixels.length - 1);
           // Calculate the convolution
           // We sum all the neighboring pixels multiplied by the values in the convolution matrix.
            rtotal+= (red(img.pixels[loc]) * matrix[i][j]);
            gtotal+= (green(img.pixels[loc]) * matrix[i][j]);
            btotal+= (blue(img.pixels[loc]) * matrix[i][j]);
        }
}
    
    //Added factor and bias
    rtotal = (rtotal * factor) + bias;
    gtotal = (gtotal * factor) + bias;
    btotal = (btotal * factor) + bias;
    
    //Make sure RGB is within range
    rtotal = constrain(rtotal,0,255);
    gtotal = constrain(gtotal,0,255);
    btotal = constrain(btotal,0,255);
    //Return the resulting color
    return color(rtotal,gtotal,btotal);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////
float[][] multiply_matrix(float[][] matrixA, float[][] matrixB) {
    //Source:  https://en.wikipedia.org/wiki/Matrix_multiplication_algorithm
    //Test:    http://www.calcul.com/show/calculator/matrix-multiplication_;2;3;3;5
    
    int n = matrixA.length;      // matrixA rows
    int m = matrixA[0].length;   // matrixA columns
    int p = matrixB[0].length;
    
    float[][] matrixC;
    matrixC = new float[n][p]; 
    
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < p; j++) {
            for (int k = 0; k < m; k++) {
                matrixC[i][j] = matrixC[i][j] + matrixA[i][k] * matrixB[k][j];
        }
        }
}
    
    //print_matrix(matrix);
    return matrixC;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////
float[][] normalize_matrix(float[][] matrix) {
    //Source:  https://www.taylorpetrick.com/blog/post/convolution-part2
    //The resulting matrix is the same size as the original, but the output range will be constrained 
    //between 0.0 and 1.0.  Useful for keeping brightness the same.
    //Do not use on a maxtix that sums to zero, such as sobel.
    
    int n = matrix.length;      // rows
    int m = matrix[0].length;   // columns
    float sum = 0;
    
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            sum +=matrix[i][j];
        }
}
    
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            matrix[i][j] = matrix[i][j] / abs(sum);
        }
}
    
    return matrix;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////
float[][] scale_matrix(float[][] matrix, int scale) {
    int n = matrix.length;      // rows
    int p = matrix[0].length;   // columns
    float sum = 0;
    
    float[][] nmatrix = new float[n * scale][p * scale];
    
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < p; j++) {
            for (int si = 0; si < scale; si++) {
                for (int sj = 0; sj < scale; sj++) {
                    //println(si, sj);
                   int a1 = (i * scale) + si;
                   int a2 = (j * scale) + sj;
                    float a3 =matrix[i][j];
                    //println(a1 + ", " + a2 + " = " + a3 );
                    //nmatrix[(i*scale)+si][(j*scale)+sj] = matrix[i][j];
                    nmatrix[a1][a2] = a3;
                }
        }
        }
        //println();
}
    //println("scale_matrix: " + scale);
    return nmatrix;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////
void print_matrix(float[][] matrix) {
    int n = matrix.length;      // rows
    int p = matrix[0].length;   // columns
    float sum = 0;
    
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < p; j++) {
            sum +=matrix[i][j];
            System.out.printf("%10.5f ", matrix[i][j]);
        }
        println();
}
    println("Sum: ", sum);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////////////////////////////
void save_screenshot() {
    //code_comment("save Screenshot");
    saveFrame(outfilename);
}
