// Very simple and inadequate greyscale conversion, but it'll do static public ByteImage ToGreyscale(ByteImage inputImage, bool alpha100 = true) { byte[] inputImageData = inputImage.imageData; int width = inputImage.width; int height = inputImage.height; int stride = inputImage.stride; byte[] output = new byte[inputImageData.Length]; int strideHere = 0; int xX4; int offsetHere; byte tmpLuma; for (int y = 0; y < height; y++) { strideHere = stride * y; for (int x = 0; x < width; x++) // 4 bc RGBA { xX4 = x * 4; offsetHere = strideHere + xX4; tmpLuma = (byte)(inputImageData[offsetHere] * 0.11 + 0.59 * inputImageData[offsetHere + 1] + 0.3 * inputImageData[offsetHere + 2]); output[offsetHere] = tmpLuma; output[offsetHere + 1] = tmpLuma; output[offsetHere + 2] = tmpLuma; output[offsetHere + 3] = alpha100 ? (byte)255 : inputImageData[offsetHere + 3]; } } return(new ByteImage(output, stride, width, height, inputImage.pixelFormat)); }
static public FloatImage FromByteImage(ByteImage inputImage) { byte[] inputImageData = inputImage.imageData; int width = inputImage.width, height = inputImage.height, stride = inputImage.stride; PixelFormat pixelFormat = inputImage.pixelFormat; float[] output = new float[inputImageData.Length]; int strideHere = 0; int xX4; int offsetHere; for (int y = 0; y < height; y++) { strideHere = stride * y; for (int x = 0; x < width; x++) // 4 bc RGBA { xX4 = x * 4; offsetHere = strideHere + xX4; output[offsetHere] = (float)inputImageData[offsetHere]; output[offsetHere + 1] = (float)inputImageData[offsetHere + 1]; output[offsetHere + 2] = (float)inputImageData[offsetHere + 2]; output[offsetHere + 3] = (float)inputImageData[offsetHere + 3]; } } return(new FloatImage(output, stride, width, height, pixelFormat)); }
public static Bitmap ByteArrayToBitmap(ByteImage byteImage) { Bitmap myBitmap = new Bitmap(byteImage.width, byteImage.height, byteImage.pixelFormat); Rectangle rect = new Rectangle(0, 0, myBitmap.Width, myBitmap.Height); System.Drawing.Imaging.BitmapData bmpData = myBitmap.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite, myBitmap.PixelFormat); bmpData.Stride = byteImage.stride; IntPtr ptr = bmpData.Scan0; System.Runtime.InteropServices.Marshal.Copy(byteImage.imageData, 0, ptr, byteImage.imageData.Length); myBitmap.UnlockBits(bmpData); return(myBitmap); }
// Simple 1D Greyscale regrade of one image to another. Basically just to match brightness, that's all. static public FloatImage Regrade1DSimpleLUT(ByteImage testImage, ByteImage referenceImage) { byte[] testImageData = testImage.imageData; byte[] referenceImageData = referenceImage.imageData; float[] output = new float[testImage.Length]; // Build histogram AverageData1D[] FactorLUT = new AverageData1D[256]; int width = testImage.width; int height = testImage.height; int strideTest = testImage.stride; int strideRef = referenceImage.stride; int strideHereTest, strideHereRef = 0; int xX4; int offsetHereTest, offsetHereRef; float testLuma, refLuma; float hereFactor; for (int y = 0; y < height; y++) { strideHereTest = strideTest * y; strideHereRef = strideRef * y; for (int x = 0; x < width; x++) // 4 bc RGBA { xX4 = x * 4; offsetHereTest = strideHereTest + xX4; offsetHereRef = strideHereRef + xX4; testLuma = testImageData[offsetHereTest] * 0.11f + 0.59f * testImageData[offsetHereTest + 1] + 0.3f * testImageData[offsetHereTest + 2]; refLuma = referenceImageData[offsetHereRef] * 0.11f + 0.59f * referenceImageData[offsetHereRef + 1] + 0.3f * referenceImageData[offsetHereRef + 2]; FactorLUT[(int)testLuma].color += refLuma / testLuma; FactorLUT[(int)testLuma].divisor++; /*output[offsetHereTest] = tmpLuma; * output[offsetHereTest + 1] = tmpLuma; * output[offsetHereTest + 2] = tmpLuma; * output[offsetHereTest + 3] = testImageData[offsetHere + 3];*/ } } // Evaluate histogram // No interpolation bc it only has to apply to this one image. for (int i = 0; i < 256; i++) { if (FactorLUT[i].divisor != 0) { FactorLUT[i].color = FactorLUT[i].color / FactorLUT[i].divisor; } } // Apply histogram for (int y = 0; y < height; y++) { strideHereTest = strideTest * y; for (int x = 0; x < width; x++) // 4 bc RGBA { xX4 = x * 4; offsetHereTest = strideHereTest + xX4; testLuma = testImageData[offsetHereTest] * 0.11f + 0.59f * testImageData[offsetHereTest + 1] + 0.3f * testImageData[offsetHereTest + 2]; hereFactor = FactorLUT[(int)testLuma].color; output[offsetHereTest] = testImageData[offsetHereTest] * hereFactor; output[offsetHereTest + 1] = testImageData[offsetHereTest + 1] * hereFactor; output[offsetHereTest + 2] = testImageData[offsetHereTest + 2] * hereFactor; output[offsetHereTest + 3] = testImageData[offsetHereTest + 3]; } } return(new FloatImage(output, strideTest, width, height, testImage.pixelFormat)); }
// Simple 1D Greyscale regrade of one image to another. Basically just to match brightness, that's all. static public FloatImage Regrade1DHistogram(ByteImage testImage, ByteImage referenceImage, int percentileSubdivisions = 100, int smoothradius = 20, float smoothIntensity = 1f) { byte[] testImageData = testImage.imageData; byte[] referenceImageData = referenceImage.imageData; float[] output = new float[testImage.Length]; // Work in 16 bit for more precision in the percentile thing. int sixteenBitCount = 256 * 256; int sixteenBitMax = sixteenBitCount - 1; // Build histogram int[] histogramTest = new int[sixteenBitCount]; int[] histogramRef = new int[sixteenBitCount]; int width = testImage.width; int height = testImage.height; int strideTest = testImage.stride; int strideRef = referenceImage.stride; int strideHereTest, strideHereRef = 0; int xX4; int offsetHereTest, offsetHereRef; int testLuma, refLuma; float hereFactor; // 1. BUILD HISTOGRAMS for (int y = 0; y < height; y++) { strideHereTest = strideTest * y; strideHereRef = strideRef * y; for (int x = 0; x < width; x++) // 4 bc RGBA { xX4 = x * 4; offsetHereTest = strideHereTest + xX4; offsetHereRef = strideHereRef + xX4; // Add 1 to avoid division by zero issues, subtract later again. testLuma = 1 + (int)Math.Max(0, Math.Min(sixteenBitMax, (((int)testImageData[offsetHereTest] << 8) * 0.11f + 0.59f * ((int)testImageData[offsetHereTest + 1] << 8) + 0.3f * ((int)testImageData[offsetHereTest + 2] << 8)))); refLuma = 1 + (int)Math.Max(0, Math.Min(sixteenBitMax, (((int)referenceImageData[offsetHereRef] << 8) * 0.11f + 0.59f * ((int)referenceImageData[offsetHereRef + 1] << 8) + 0.3f * ((int)referenceImageData[offsetHereRef + 2] << 8)))); histogramTest[testLuma]++; histogramRef[refLuma]++; /*output[offsetHereTest] = tmpLuma; * output[offsetHereTest + 1] = tmpLuma; * output[offsetHereTest + 2] = tmpLuma; * output[offsetHereTest + 3] = testImageData[offsetHere + 3];*/ } } // Info: The subdivision count is the amount of "boxes" I divide the brightness spectrum into. But these arrays do not represent the boxes, but rather the splits between the boxes, so it's -1. The splits define exactly WHERE the boxes end. FloatIssetable[] percentilesTest = new FloatIssetable[percentileSubdivisions - 1]; FloatIssetable[] percentilesRef = new FloatIssetable[percentileSubdivisions - 1]; float onePercentile = width * height / percentileSubdivisions; // This is a bit messy I guess but it should work out. // 2. BUILD PERCENTILE ARRAYS // Fill percentile-arrays, basically saying at which brightness a certain percentile starts, by the amount of pixels that have a certain brightness int countTest = 0; int currentPercentileTest = 0, lastPercentileTest = 0; float lastTestPercentileJumpValue = 0; int countRef = 0; int currentPercentileRef = 0, lastPercentileRef = 0; float lastRefPercentileJumpValue = 0; for (int i = 0; i < sixteenBitCount; i++) { countTest += histogramTest[i]; currentPercentileTest = (int)Math.Min(percentileSubdivisions - 1, Math.Floor((double)countTest / onePercentile)); if (currentPercentileTest != lastPercentileTest) { percentilesTest[currentPercentileTest - 1].value = i; percentilesTest[currentPercentileTest - 1].isSet = true; // Fill holes, if there are any. for (int a = lastPercentileTest + 1; a < currentPercentileTest; a++) { percentilesTest[a - 1].value = lastTestPercentileJumpValue + (i - lastTestPercentileJumpValue) * ((a - lastPercentileTest) / ((float)currentPercentileTest - lastPercentileTest)); percentilesTest[a - 1].isSet = true; } lastTestPercentileJumpValue = i; } lastPercentileTest = currentPercentileTest; countRef += histogramRef[i]; currentPercentileRef = (int)Math.Min(percentileSubdivisions - 1, Math.Floor((double)countRef / onePercentile)); if (currentPercentileRef != lastPercentileRef) { percentilesRef[currentPercentileRef - 1].value = i; percentilesRef[currentPercentileRef - 1].isSet = true; // Fill holes, if there are any. for (int a = lastPercentileRef + 1; a < currentPercentileRef; a++) { //percentilesRef[a - 1].value = lastRefPercentileJumpValue + (i - lastRefPercentileJumpValue) * (currentPercentileRef - lastPercentileRef) / a; percentilesRef[a - 1].value = lastRefPercentileJumpValue + (i - lastRefPercentileJumpValue) * ((a - currentPercentileRef) / ((float)currentPercentileRef - lastPercentileRef)); percentilesRef[a - 1].isSet = true; } lastRefPercentileJumpValue = i; } lastPercentileRef = currentPercentileRef; } // 3. BUILD LUT FROM PERCENTILE ARRAYS FloatIssetable[] factorsLUT = new FloatIssetable[256]; for (int i = 0; i < percentilesTest.Length; i++) { factorsLUT[(int)Math.Floor(percentilesTest[i].value / 256)].value = percentilesTest[i].value == 0 ? 0 : percentilesRef[i].value / percentilesTest[i].value; factorsLUT[(int)Math.Floor(percentilesTest[i].value / 256)].isSet = true; } // 4. FILL HOLES IN LUT bool lastExists = false, nextExists = false; int lastExisting = 0, nextExisting = 0; float linearInterpolationFactor = 0; for (int i = 0; i < 256; i++) { if (factorsLUT[i].isSet == false) { // Find next set value, if it exisst nextExists = false; for (int a = i + 1; a < 256; a++) { if (factorsLUT[a].isSet) { nextExists = true; nextExisting = a; break; } } if (nextExists && lastExists) { linearInterpolationFactor = ((float)i - lastExisting) / (nextExisting - lastExisting); factorsLUT[i].value = factorsLUT[lastExisting].value + linearInterpolationFactor * (factorsLUT[nextExisting].value - factorsLUT[lastExisting].value); factorsLUT[i].isSet = true; lastExists = true; lastExisting = i; } else if (lastExists) { factorsLUT[i].value = factorsLUT[lastExisting].value; factorsLUT[i].isSet = true; lastExists = true; lastExisting = i; } else if (nextExists) { factorsLUT[i].value = factorsLUT[nextExisting].value; factorsLUT[i].isSet = true; lastExists = true; lastExisting = i; } else if (!nextExists && !lastExists) { // Kinda impossible but ok // I think we're kinda fucced then. Let's just assume this never happens } } else { // Do nothing, all good lastExists = true; lastExisting = i; } } // 5. SMOOTH LUT // Simple 1D "Box" blur. So, a line blur? // TODO find a way to protect blacks if (smoothIntensity > 0) { float[] elevation = new float[factorsLUT.Length - 1]; float[] elevationSmoothed = new float[factorsLUT.Length - 1]; float totalElevation = 0; float totalElevationSmoothed = 0; float blackPoint = float.PositiveInfinity; float whitePoint = 0; float startPoint = factorsLUT[0].value; if (factorsLUT[0].value > whitePoint) { whitePoint = factorsLUT[0].value; } if (factorsLUT[0].value < blackPoint) { blackPoint = factorsLUT[0].value; } float blackPointSmoothed = blackPoint; float whitePointSmoothed = whitePoint; // We are not smoothing the raw data but the rise. for (int i = 0; i < elevation.Length; i++) { elevation[i] = factorsLUT[i + 1].value - factorsLUT[i].value; totalElevation += elevation[i]; if (factorsLUT[i + 1].value > whitePoint) { whitePoint = factorsLUT[i + 1].value; } if (factorsLUT[i + 1].value < blackPoint) { blackPoint = factorsLUT[i + 1].value; } } double averageValue = 0; double averageDivisor = 0; float invertedSmoothIntensity = 1 - smoothIntensity; float currentRealValueWithSmoothing = factorsLUT[0].value; FloatIssetable[] factorsLUTSmoothed = new FloatIssetable[factorsLUT.Length]; for (int i = 0; i < 255; i++) { if (i == 0) { // let's say radius 5 // goes up to 4, so: 0 1 2 3 4 5 for (int a = 0; a <= smoothradius; a++) { averageValue += elevation[a]; averageDivisor += 1; } } else { // i == 5 // 0 1 2 3 4 5 if (i <= smoothradius) { averageValue += elevation[0]; averageDivisor++; } else { // i == 6 : 0 1 2 3 4 5 6 // i-5 = 1. i-5-1 = 0 averageValue -= elevation[i - (smoothradius - 1)]; averageDivisor--; } // i + 5 = 254 : i == 249: 249 250 251 252 253 254 if (i + smoothradius < 255) { averageValue += elevation[i + smoothradius]; averageDivisor++; } else { averageValue -= elevation[254]; averageDivisor--; } } elevationSmoothed[i] = (float)(invertedSmoothIntensity * elevation[i] + smoothIntensity * (averageValue / averageDivisor)); totalElevationSmoothed += elevationSmoothed[i]; currentRealValueWithSmoothing += elevationSmoothed[i]; if (currentRealValueWithSmoothing > whitePointSmoothed) { whitePointSmoothed = currentRealValueWithSmoothing; } if (currentRealValueWithSmoothing < blackPointSmoothed) { blackPointSmoothed = currentRealValueWithSmoothing; } } // Need a last pass to make sure blacks and whites are still the same, thus: float scaleFactor = (whitePointSmoothed - blackPointSmoothed) / (whitePoint - blackPoint); for (int i = 0; i < 255; i++) { elevationSmoothed[i] /= scaleFactor; } // Transfer back to FactorsLUT for (int i = 0; i < 255; i++) { factorsLUT[i + 1].value = factorsLUT[i].value + elevationSmoothed[i]; } } // 6. APPLY LUT for (int y = 0; y < height; y++) { strideHereTest = strideTest * y; for (int x = 0; x < width; x++) // 4 bc RGBA { xX4 = x * 4; offsetHereTest = strideHereTest + xX4; testLuma = (int)Math.Max(0, Math.Min(sixteenBitMax, (((float)testImageData[offsetHereTest]) * 0.11f + 0.59f * ((float)testImageData[offsetHereTest + 1]) + 0.3f * ((float)testImageData[offsetHereTest + 2])))); hereFactor = factorsLUT[(int)testLuma].value; output[offsetHereTest] = (float)testImageData[offsetHereTest] * hereFactor; output[offsetHereTest + 1] = (float)testImageData[offsetHereTest + 1] * hereFactor; output[offsetHereTest + 2] = (float)testImageData[offsetHereTest + 2] * hereFactor; output[offsetHereTest + 3] = (float)testImageData[offsetHereTest + 3]; } } return(new FloatImage(output, strideTest, width, height, testImage.pixelFormat)); }