/// <summary> /// Initialize the container based on Texture2D source, and does some conversions if required. /// </summary> /// <param name="source">Source.</param> /// <param name="convertToPureBW">If set to <c>true</c> convert to pure B&W </param> /// <param name="doBradleyTreshold">If set to <c>true</c> do bradley treshold [binarize].</param> public DrawableTextureContainer(Texture2D source, bool convertToPureBW, bool doBradleyTreshold) { width = source.width; height = source.height; dirtyArea = new TextureRegion(); isDirty = false; texture = new Texture2D(width, height, DrawableTextureContainer.TextureFormatUsed(), false); texture.SetPixels32(source.GetPixels32()); texture.Apply(); pixels = new Color32[width * height]; pixels = source.GetPixels32(); offsetTable = new int[height]; for (int i = 0; i < height; i++) { offsetTable [i] = i * width; } if (convertToPureBW) { if (doBradleyTreshold) { BradleyLocalTreshold(); } else { ConvertToPureBW(0.5f); } } }
/// <summary> /// Initializes a new instance of the <see cref="pl.ayground.IntegralImage"/> class. /// </summary> /// <param name="container">DrawableContainer to use as a base</param> public IntegralImage(DrawableTextureContainer container) { width = container.getWidth(); height = container.getHeight(); image = new float[width + 1, height + 1]; for (int y = 1; y < height + 1; y++) { float rowSum = 0; for (int x = 1; x < width + 1; x++) { rowSum += container.getGrayFloat(x - 1, y - 1); image [x, y] = rowSum + image [x, y - 1]; } } }
/// <summary> /// FloodFill function for the image. /// </summary> /// <returns><c>true</c>, if image was filled, <c>false</c> otherwise.</returns> /// <param name="x">The x coordinate.</param> /// <param name="y">The y coordinate.</param> /// <param name="image">Image</param> /// <param name="color">Color</param> /// <param name="drawOnBlack">If set to <c>true</c> draw on black. This is used to avoid painting on black lines and destroying the image</param> public bool fillImageWithContainer(int x, int y, DrawableTextureContainer image, Color32 color, bool drawOnBlack) { int width = image.getWidth(); int height = image.getHeight(); if (x < 0 || x > width) { return(false); } if (y < 0 || y >= height) { return(false); } Color32 targetColor = image.pixels [x + image.offsetTable [y]]; if (compare(targetColor, color)) { return(false); // No coloring if the target is already using valid color } if (compare(targetColor, DrawableTextureContainer.BLACK)) { if (!drawOnBlack) { return(false); // No coloring in case you do not want to paint on BLACK and the pixel you targeted is BLACK } } int count = 0; Queue <imgPixel> q = new Queue <imgPixel> (); q.Enqueue(new imgPixel(x, y)); do { if (count++ > DrawableTextureContainer.COLORING_FAILSAFE_LIMIT) { break; } // Take the next pixel from coloring queue imgPixel n = q.Dequeue(); // Proceed if the pixel in queue is in color that requires change (target color) if (compare(image.pixels [n.x + image.offsetTable [n.y]], targetColor)) { // In THREE simple steps. //1. Color it. image.pixels [n.x + image.offsetTable [n.y]] = color; // This two below are helper values that track if I need to color the line above and below my current target pixel. bool InitialBottomAddRequired = true; bool InitialTopAddRequired = true; // Add the pixels above and below current pixel to the queue if its required. if (n.y + 1 < height) { if (compare(image.pixels [n.x + image.offsetTable [n.y + 1]], targetColor)) { q.Enqueue(new imgPixel(n.x, n.y + 1)); // Now it will not be required to add them in next loop InitialBottomAddRequired = false; } } if (n.y - 1 > 0) { if (compare(image.pixels [n.x + image.offsetTable [n.y - 1]], targetColor)) { q.Enqueue(new imgPixel(n.x, n.y - 1)); // Now it will not be required to add them in next loop InitialTopAddRequired = false; } } // 2. Coloring To the Left int tmpX = n.x - 1; bool addTopRequired = InitialTopAddRequired; bool addBottomRequired = InitialBottomAddRequired; while (tmpX >= 0) { if (compare(image.pixels [tmpX + image.offsetTable [n.y]], targetColor)) { image.pixels [tmpX + image.offsetTable [n.y]] = color; // Here the real color change happens. if (n.y > 0) { if (compare(image.pixels [tmpX + image.offsetTable [n.y - 1]], targetColor)) { if (addTopRequired) { // I add the pixel above to the coloring queue if thats required q.Enqueue(new imgPixel(tmpX, n.y - 1)); addTopRequired = false; } } else { // when I reach a pixel above that is not in TargetColor I do need to take note of that so I add the Top pixel again when valid addTopRequired = true; } } if (n.y < height - 1) // same logic for bottom pixels { if (compare(image.pixels [tmpX + image.offsetTable [n.y + 1]], targetColor)) { if (addBottomRequired) { q.Enqueue(new imgPixel(tmpX, n.y + 1)); addBottomRequired = false; } } else { addBottomRequired = true; } } tmpX--; } else { break; } } //3 Coloring to the right now - same logic as to the left tmpX = n.x + 1; // seting the top/bottom requirements addTopRequired = InitialTopAddRequired; addBottomRequired = InitialBottomAddRequired; while (tmpX < width) { if (compare(image.pixels [tmpX + image.offsetTable [n.y]], targetColor)) { image.pixels [tmpX + image.offsetTable [n.y]] = color; if (n.y > 0) { if (compare(image.pixels [tmpX + image.offsetTable [n.y - 1]], targetColor)) { if (addTopRequired) { q.Enqueue(new imgPixel(tmpX, n.y - 1)); addTopRequired = false; } } else { addTopRequired = true; } } if (n.y < height - 1) { if (compare(image.pixels [tmpX + image.offsetTable [n.y + 1]], targetColor)) { if (addBottomRequired) { q.Enqueue(new imgPixel(tmpX, n.y + 1)); addBottomRequired = false; } } else { addBottomRequired = true; } } tmpX++; } else { break; } } } } while (q.Count > 0); // Mark the whole image as dirty - could be further optimized to track the real DirtyArea (but will need processing time for tracking the DirtyArea Growth, not doing it right not). image.MarkFullAsDirty(); return(true); }