package com.photoembroidery.tat.olsennoise; //MIT License, use for anything you want commercial or open, with or without attribution. import java.util.Arrays; public class OlsenNoise2D { public static final int MAX_ITERATIONS = 7; //just definition for other classes to use. public static final int SCALE_FACTOR = 2; //The scale factor is kind of arbitrary, but the code is only consistent for 2 currently. Gives noise for other scale but not location proper. private static final int[][] blur3x3 = new int[][]{ {1, 1, 1}, {1, 1, 1}, {1, 1, 1} }; // Matrix for the blur. private static final int blurEdge = 2; //extra pixels are needed for the blur (3 - 1). //This function has a soft coded iteration. /** * Function adds all the required pixels into the pixels array. * Note that the scanline should not actually equal the width. * It should be larger as per the getRequiredDim function. * * @param iterations Number of iterations to perform. * @param pixels pixel array to be used to insert values. (Pass by reference) * @param stride distance in the array to the next y value. * @param x requested X location. * @param y requested Y location. * @param width width of the image. * @param height height of the image. */ public static void olsennoise( int iterations, int[] pixels, int stride, int x, int y, int width, int height) { olsennoise(pixels, stride, x, y, width, height, iterations); //Calls the main routine. applyColor(pixels, stride, width, height); } /** * Places all the grayscale colors of Olsen Noise into the array, provided. * @param pixels pixel array to be used to insert values. (Pass by reference) * @param stride distance in the array to the next y value. * @param x requested X location. * @param y requested Y location. * @param width width of the image. * @param height height of the image. */ public static void olsennoise(int[] pixels, int stride, int x, int y, int width, int height) { olsennoise(pixels, stride, x, y, width, height, MAX_ITERATIONS); applyColor(pixels, stride, width, height); } /** * Converts a dimension into the dimension required by the algorithm. * Due to the blurring, to get valid data the array must be slightly larger. * Due to the interpixel location at lowest levels it needs to be bigger by * the max value that can be. (SCALE_FACTOR) * @param dim * @return */ public static int getRequiredDim(int dim) { return dim + blurEdge + SCALE_FACTOR; } //Function inserts the values into the given pixels array (pass by reference) //The results will be within 0-255 assuming the requested iterations are 7. private static void olsennoise(int[] pixels, int stride, int x_within_field, int y_within_field, int width, int height, int iteration) { if (iteration == 0) { //Base case. If we are at the bottom. Do not run the rest of the function. Return random values. clearValues(pixels, stride, width, height); //base case needs zero, apply Noise will not eat garbage. applyNoise(pixels, stride, x_within_field, y_within_field, width, height, iteration); return; } int x_remainder = x_within_field & 1; //Adjust the x_remainder so we know how much more into the pixel are. int y_remainder = y_within_field & 1; //Math.abs(y_within_field % SCALE_FACTOR) - Would be assumed for larger scalefactors. /* Pass the pixels, and the stride for that set of pixels. Recurse the call to the function moving the x_within_field forward if we actaully want half a pixel at the start. Same for the y. The width should expanded by the x_remainder, and then half the size, with enough extra to store the extra pixels from the blur. If the width is too long, it'll just run more stuff than it needs to. */ olsennoise(pixels, stride, ((x_within_field + x_remainder) / SCALE_FACTOR) - x_remainder, ((y_within_field + y_remainder) / SCALE_FACTOR) - y_remainder, ((width + x_remainder) / SCALE_FACTOR) + blurEdge, ((height + y_remainder) / SCALE_FACTOR) + blurEdge, iteration - 1); //This will scale the image from half the width and half the height. bounds. //The scale function assumes you have at least width/2 and height/2 good pixels. //We requested those from olsennoise above, so we should have that. applyScale(pixels, stride, width + blurEdge, height + blurEdge, SCALE_FACTOR); //We shift the pixels over. By the amount within those pixels we want. //We sometimes want half a pixel into a scaled pixel. applyShift(pixels, stride, x_remainder, y_remainder, width + blurEdge, height + blurEdge); //This applies the blur and uses the given bounds. //Since the blur loses two at the edge, this will result //in us having width x height of good pixels and required // width + blurEdge of good pixels. height + blurEdge of good pixels. applyBlur(pixels, stride, width + blurEdge, height + blurEdge); //Applies noise to all the given pixels. Does not require more or less than pixels. Just offsets them all randomly. applyNoise(pixels, stride, x_within_field, y_within_field, width, height, iteration); } private static void applyNoise(int[] pixels, int stride, int x_within_field, int y_within_field, int width, int height, int iteration) { int index = 0; for (int k = 0, n = height - 1; k <= n; k++, index += stride) { //iterate the y positions. Offsetting the index by stride each time. for (int j = 0, m = width - 1; j <= m; j++) { //iterate the x positions through width. int current = index + j; // The current position of the pixel is the index which will have added stride each, y iteration pixels[current] += (hashrandom(j + x_within_field, k + y_within_field, iteration) & (1 << (7 - iteration))); //add on to this pixel the hash function with the set reduction. //The amount of randomness here somewhat arbitary. Just have it give self-normalized results 0-255. //It simply must scale down with the larger number of iterations. } } } private static void applyScale(int[] pixels, int stride, int width, int height, int factor) { int index = (height - 1) * stride; //We must iteration backwards to scale so index starts at last Y position. for (int k = 0, n = height - 1; k <= n; n--, index -= stride) { // we iterate the y, removing stride from index. for (int j = 0, m = width - 1; j <= m; m--) { // iterate the x positions from width to 0. int current = index + m; //current position is the index (position of that scanline of Y) plus our current iteration in scale. int lower = ((n / factor) * stride) + (m / factor); //We find the position that is half that size. From where we scale them out. pixels[current] = pixels[lower]; // Set the outer position to the inner position. Applying the scale. } } } //This function is to insure position stability with interpixel locations. //the pixels at the lowest iteration get scaled up, and we need positions within this pixels at the higher level. //We'd still get noise without this function, it would just line up with the lower pixels. private static void applyShift(int[] pixels, int stride, int shiftX, int shiftY, int width, int height) { if ((shiftX == 0) && (shiftY == 0)) { //if we aren't actually trying to move it. return; //return } int index; int indexoffset = shiftX + (shiftY * stride); //The offset within the array that that shift would correspond to. //Since down and to the right is still (stride + 1) every loop. We preset it. index = 0; for (int k = 0, n = height - 1; k <= n; k++, index += stride) { // iterate the y values, add stride to index. for (int j = 0, m = width - 1; j <= m; j++) { //iterate the x values with j. int current = index + j; // current position is all our added up stride values, and our position in the X. pixels[current] = pixels[current + indexoffset]; //set the current pixel equal to the one shifted by offset. } } } //colorizes the image. This takes our values between 0-255 and makes them pixels with alpha. //0xAARRGGBB private static void applyColor(int[] pixels, int stride, int width, int height) { int index; index = 0; for (int k = 0, n = height - 1; k <= n; k++, index += stride) { //iterate the y values. for (int j = 0, m = width - 1; j <= m; j++) { //iterate the x values. int current = index + j; // current position is the sum of the strides plus the position in the x. int pixel = pixels[current]; //get the current pixel. pixels[current] = 0xFF000000 | pixel << 16 | pixel << 8 | pixel; //turn it into a greyscale. } } } private static void clearValues(int[] pixels, int stride, int width, int height) { int index; index = 0; for (int k = 0, n = height - 1; k <= n; k++, index += stride) { //iterate the y values. for (int j = 0, m = width - 1; j <= m; j++) { //iterate the x values. int current = index + j; // current position is the sum of the strides plus the position in the x. int pixel = pixels[current]; //get the current pixel. pixels[current] = 0; //clears those values. } } } //Applies the blur. private static void applyBlur(int[] pixels, int stride, int width, int height) { //Calls my convolve routine, with the matrix we setup initially. convolve(pixels, 0, stride, 0, 0, width, height, blur3x3); } /** * Memory Free In-Place Convolution. * * It is modified to not actually do all the color blending work. * The values passed to it are between 0-255 * So it does a proper average. * * @param pixels pixels to be modified (pass by reference). * @param offset offset within the pixel array to call zero. * @param stride width of the memory block to next Y. * @param x the start x value. * @param y the start y value. * @param width the width of blocks to be used for the convolution. * @param height the height of the convolution area. * @param matrix matrix of the convolution. */ public static void convolve(int[] pixels, int offset, int stride, int x, int y, int width, int height, int[][] matrix) { int index = offset + x + (y*stride); //index is where we are in the pixels. All equal 0 for our use. Y=0, X=0, offset = 0. for (int j = 0; j < height; j++, index += stride) { // iterate the y values. adding stride to index each time. for (int k = 0; k < width; k++) {//iterate the x values int pos = index + k; //current position sum of the strides and the position in the x. pixels[pos] = convolve(pixels,stride,pos, matrix); //convolves the matrix down and to the right from the current position. //this is somewhat non-standard, but that's because everybody's been doing it wrong for basically ever. } } } //crimps the values between 255 and 0. Required for some other convolutions like emboss where they go out of register. private static int crimp(int color) { return (color >= 0xFF) ? 0xFF : (color < 0) ? 0 : color; } //performs the convolution on that pixel by the given matrix. //Note all values within the matrix are down and to the right from the current pixel. //None are up or to the left. This is by design. private static int convolve(int[] pixels, int stride, int index, int[][] matrix) { int parts = 0; int sum = 0; int factor; for (int j = 0, m = matrix.length; j < m; j++, index+=stride) { //iterates the matrix for (int k = 0, n = matrix[j].length; k < n; k++) { //iterates the matrix[] within. factor = matrix[j][k]; //gets the multiple from that matrix. parts += factor; //keeps a running total for the parts. sum += factor * pixels[index + k]; //keeps a total of the sum of the factors and the pixels they correspond to. } } if (parts == 0) return crimp(sum); return crimp(sum/parts); } /** * XOR hash the hashed values of each element, in elements * @param elements elements to be hashed and xor'ed together. * @return */ public static int hashrandom(int... elements) { long hash = 0; for (int i = 0; i < elements.length; i++) { hash ^= elements[i]; hash = hash(hash); } return (int) hash; } private static long hash(long v) { long hash = v; long h = hash; switch ((int) hash & 3) { case 3: hash += h; hash ^= hash << 32; hash ^= h << 36; hash += hash >> 22; break; case 2: hash += h; hash ^= hash << 22; hash += hash >> 34; break; case 1: hash += h; hash ^= hash << 20; hash += hash >> 2; } hash ^= hash << 6; hash += hash >> 10; hash ^= hash << 8; hash += hash >> 34; hash ^= hash << 50; hash += hash >> 12; return hash; } /** * Trim off the edge pixels if you need such a function. * Gets rid of the side bits. * * @param pixels destination array * @param width Width of the new image * @param height height of the new image * @param workingpixels source array * @param workingstride stride of source array */ public static void trim(int[] pixels, int width, int height, int[] workingpixels, int workingstride) { for (int k = 0; k < height; k++) { for (int j = 0; j < width; j++) { int index = j + (k * width); int workingindex = j + (k * workingstride); pixels[index] = workingpixels[workingindex]; } } } }