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 Vector3Image(ByteImage inputImage) { byte[] inputImageData = inputImage.imageData; width = inputImage.width; height = inputImage.height; originalStride = inputImage.stride; PixelFormat pixelFormat = inputImage.pixelFormat; imageData = new Vector3[width * height]; int strideHere = 0; int pixelOffset; int offsetHere; int pixelMultiplier = 3; if (inputImage.pixelFormat == PixelFormat.Format32bppArgb) { pixelMultiplier = 4; } for (int y = 0; y < height; y++) { strideHere = originalStride * y; for (int x = 0; x < width; x++) // 4 bc RGBA { pixelOffset = x * pixelMultiplier; offsetHere = strideHere + pixelOffset; imageData[y * width + x].X = inputImageData[offsetHere]; imageData[y * width + x].Y = inputImageData[offsetHere + 1]; imageData[y * width + x].Z = inputImageData[offsetHere + 2]; } } }
// 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)); }
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); }
private void drawStats(int maxIndex) { int imageWidth = (int)stats_img_container.ActualWidth; int imageHeight = (int)stats_img_container.ActualHeight; if (imageWidth < 5 || imageHeight < 5) { return; // avoid crashes and shit } // We flip imageHeight and imageWidth because it's more efficient to work on rows than on columns. We later rotate the image into the proper position ByteImage statsImage = Helpers.BitmapToByteArray(new Bitmap(imageHeight, imageWidth, System.Drawing.Imaging.PixelFormat.Format24bppRgb)); int stride = statsImage.stride; int strideHere; double verticalScaleFactorFps = highestFPS == 0 ? 0: (imageHeight - 1) / highestFPS; double verticalScaleFactorSmallestDifference = highestSmallestDifference == 0 ? 1: (imageHeight - 1) / highestSmallestDifference; double indiziPerPixel = (double)maxIndex / (double)imageWidth; int indiziPerPixelRounded = (int)Math.Ceiling(indiziPerPixel); int pixelValueAddedPerValue = (int)Math.Max(1, Math.Ceiling(255.0 / indiziPerPixel)); float[] currentColumnValuesFps = new float[indiziPerPixelRounded]; float[] currentColumnValuesSmallestDifference = new float[indiziPerPixelRounded]; double averageFpsForColumnCounter; double averageSmallestDifferenceForColumnCounter; int rangeStart = 0; int yPosition; double yPositionUnrounded; // Remember, x/y in the actual Bitmap data are flipped! Hence having the outer for be X makes more sense for (int x = 0; x < imageWidth; x++) { strideHere = x * stride; rangeStart = (int)Math.Floor((indiziPerPixel * (double)x)); Array.Copy(fps, rangeStart, currentColumnValuesFps, 0, Math.Min(indiziPerPixelRounded, fps.Length - rangeStart)); Array.Sort(currentColumnValuesFps); Array.Copy(smallestDifferences, rangeStart, currentColumnValuesSmallestDifference, 0, Math.Min(indiziPerPixelRounded, smallestDifferences.Length - rangeStart)); Array.Sort(currentColumnValuesSmallestDifference); averageFpsForColumnCounter = 0; averageSmallestDifferenceForColumnCounter = 0; for (int i = 0; i < indiziPerPixelRounded; i++) // fps { yPositionUnrounded = currentColumnValuesFps[i] * verticalScaleFactorFps; yPosition = (int)yPositionUnrounded; averageFpsForColumnCounter += yPositionUnrounded; statsImage.imageData[strideHere + yPosition * 3 + 2] = statsImage.imageData[strideHere + yPosition * 3 + 2] == 255 ? (byte)255 : (byte)Math.Min(255, statsImage.imageData[strideHere + yPosition * 3 + 2] + (byte)pixelValueAddedPerValue); } yPosition = averageFpsForColumnCounter == 0 ? 0: (int)(averageFpsForColumnCounter / (double)indiziPerPixelRounded); statsImage.imageData[strideHere + yPosition * 3 + 2] = 255; for (int i = 0; i < indiziPerPixelRounded; i++) // Smallest differences { yPositionUnrounded = currentColumnValuesSmallestDifference[i] * verticalScaleFactorSmallestDifference; yPosition = (int)yPositionUnrounded; averageSmallestDifferenceForColumnCounter += yPositionUnrounded; statsImage.imageData[strideHere + yPosition * 3 + 1] = statsImage.imageData[strideHere + yPosition * 3 + 1] == 255 ? (byte)255 : (byte)Math.Min(255, statsImage.imageData[strideHere + yPosition * 3 + 1] + (byte)pixelValueAddedPerValue); } yPosition = averageSmallestDifferenceForColumnCounter == 0 ? 0 : (int)(averageSmallestDifferenceForColumnCounter / (double)indiziPerPixelRounded); statsImage.imageData[strideHere + yPosition * 3 + 1] = 255; for (int y = 0; y < imageHeight; y++) // Apply gamma to make it more visible { statsImage.imageData[strideHere + y * 3 + 1] = (byte)Math.Min(255, 255 * Math.Pow(((double)statsImage.imageData[strideHere + y * 3 + 1] / 255), 1.0 / 3.0)); statsImage.imageData[strideHere + y * 3 + 2] = (byte)Math.Min(255, 255 * Math.Pow(((double)statsImage.imageData[strideHere + y * 3 + 2] / 255), 1.0 / 3.0)); } } Bitmap statsImageBitmap = Helpers.ByteArrayToBitmap(statsImage); statsImageBitmap.RotateFlip(RotateFlipType.Rotate270FlipNone); int padding = 2; if (imageWidth > 200 && imageHeight > 100) { Graphics g = Graphics.FromImage(statsImageBitmap); g.SmoothingMode = SmoothingMode.AntiAlias; g.InterpolationMode = InterpolationMode.HighQualityBicubic; g.PixelOffsetMode = PixelOffsetMode.HighQuality; g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit; string fpsString = "Max FPS: " + highestFPS; string lowestDifferenceString = "Biggest difference: " + highestSmallestDifference; SizeF fpsStringSize = g.MeasureString(fpsString, new Font("Tahoma", 8)); SizeF lowestDifferenceStringSize = g.MeasureString(lowestDifferenceString, new Font("Tahoma", 8)); if ((fpsStringSize.Width + padding) < imageWidth && (fpsStringSize.Height + padding) < imageHeight) { RectangleF rectf = new RectangleF(padding, padding, fpsStringSize.Width, fpsStringSize.Height); g.DrawString(fpsString, new Font("Tahoma", 8), System.Drawing.Brushes.Red, rectf); } if ((lowestDifferenceStringSize.Width + padding) < imageWidth && (fpsStringSize.Height + padding * 2 + lowestDifferenceStringSize.Height) < imageHeight) { RectangleF rectf = new RectangleF(padding, lowestDifferenceStringSize.Height + padding * 2, lowestDifferenceStringSize.Width, lowestDifferenceStringSize.Height); g.DrawString(lowestDifferenceString, new Font("Tahoma", 8), System.Drawing.Brushes.Green, rectf); } g.Flush(); } stats_img.Source = Helpers.BitmapToImageSource(statsImageBitmap); }
// 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)); }