I would like to do grabcut which uses a depth map that cuts away far objects, that is used in mixed reality application. So I would like to show just the front of what I see and the background as virtual reality scene.
The problem right now I tried to adapt so code and what I get is front which is cut but in black color, the mask actually.
I don't know where is the problem settle.
The input is a depth map from zed camera.
here is a picture of the behaviour:
My trial:
private void convertToGrayScaleValues(Mat mask)
{
int width = mask.rows();
int height = mask.cols();
byte[] buffer = new byte[width * height];
mask.get(0, 0, buffer);
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
int value = buffer[y * width + x];
if (value == Imgproc.GC_BGD)
{
buffer[y * width + x] = 0; // for sure background
}
else if (value == Imgproc.GC_PR_BGD)
{
buffer[y * width + x] = 85; // probably background
}
else if (value == Imgproc.GC_PR_FGD)
{
buffer[y * width + x] = (byte)170; // probably foreground
}
else
{
buffer[y * width + x] = (byte)255; // for sure foreground
}
}
}
mask.put(0, 0, buffer);
}
For Each depth frame from Camera:
Mat erodeElement = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(4, 4));
Mat dilateElement = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(7, 7));
depth.copyTo(maskFar);
Core.normalize(maskFar, maskFar, 0, 255, Core.NORM_MINMAX, CvType.CV_8U);
Imgproc.cvtColor(maskFar, maskFar, Imgproc.COLOR_BGR2GRAY);
Imgproc.threshold(maskFar, maskFar, 180, 255, Imgproc.THRESH_BINARY);
Imgproc.dilate(maskFar, maskFar, erodeElement);
Imgproc.erode(maskFar, maskFar, dilateElement);
Mat bgModel = new Mat();
Mat fgModel = new Mat();
Imgproc.grabCut(image, maskFar, new OpenCVForUnity.CoreModule.Rect(), bgModel, fgModel, 1, Imgproc.GC_INIT_WITH_MASK);
convertToGrayScaleValues(maskFar); // back to grayscale values
Imgproc.threshold(maskFar, maskFar, 180, 255, Imgproc.THRESH_TOZERO);
Mat foreground = new Mat(image.size(), CvType.CV_8UC4, new Scalar(0, 0, 0));
image.copyTo(foreground, maskFar);
Utils.fastMatToTexture2D(foreground, texture);
In this case, the graph cut on the depth image might not be the correct method to solve all of your issue.
If you insist the processing should be done in the depth image. To find everything that is not on the table and filter out the table part. You may first apply the disparity based approach for finding the object that's is not on the ground. Reference: https://github.com/windowsub0406/StereoVision
Then based on the V disparity output image, find the locally connected component that is grouped together. You may follow this link how to do this disparity map in OpenCV which is asking the similar way to find the objects that's not on the ground
If you are ok with RGB based approaches, then use any deep learning-based method to recognize the monitor should be the correct approaches. It can directly detect the mointer bounding box. By apply this bounding box to the depth image, you may have what you want. For deep learning based approaches, there are many available package such as Yolo series. You may find one that is suitable for you. reference: https://medium.com/#dvshah13/project-image-recognition-1d316d04cb4c
Related
Please Note - This is a Math question essentially. However, i have also tagged C#
as this is the language i am working in
Summary
I'm looking for an algorithm (or name thereof) that can find the Negative Space (or space) in an image. The closest i have found Dijkstra's algorithm (which is seemingly close), yet its actually a subset of the actual problem. Namely, to walk through a Cartesian Plane traversing every coordinate that isn't filled (or black in my case) to find a mask. Example below
Example of Dijkstra's Algorithm
The background
I need to tidy up 10's of thousands of images that have artefacts in them. By cleaning up i mean these things specifically :
Using Edge Detection to find the edges of the objects in the images
Masking the Negative Space so i can covert the image backgrounds to plain white
Cropping the images to their optimal size.
Currently i'm using Canny Edge Detection to find the most important part of the image. I can crop the image fairly well (shown below), and also find all the images that have the problem. However i am having trouble locating the best algorithm (or name thereof) to find the negative space.
Example of the original image
As you can see the image looks pretty clean, however its not
Example of the accentuated problem
The image has lots of artefacts in the background and they need to be removed
Example of Canny Edge Detection
This does a wonderful job of cleaning up the image
The Problem
Dijkstra's algorithms premise is it looks for all the possible paths, its basically a solves the Travelling Sales man problem
The problems is; The algorithm actually does much more than i need to do with regards to the weighing and the distance measures , and it stops when it has the shortest path (where i need it to complete the image).
The pseudo code
1 function Dijkstra(Graph, source):
2
3 create vertex set Q
4
5 for each vertex v in Graph: // Initialization
6 dist[v] ← INFINITY // Unknown distance from source to v
7 prev[v] ← UNDEFINED // Previous node in optimal path from source
8 add v to Q // All nodes initially in Q (unvisited nodes)
9
10 dist[source] ← 0 // Distance from source to source
11
12 while Q is not empty:
13 u ← vertex in Q with min dist[u] // Node with the least distance
14 // will be selected first
15 remove u from Q
16
17 for each neighbor v of u: // where v is still in Q.
18 alt ← dist[u] + length(u, v)
19 if alt < dist[v]: // A shorter path to v has been found
20 dist[v] ← alt
21 prev[v] ← u
22
23 return dist[], prev[]
Can anyone suggest an Algorithm or modify the Pseudo Code to Dijkstra's Algorithms to achieve this?
The answer to the question was simply the Flood-fill Algorithm.
However, to solve the entire problem of cleaning subtle artefacts from images, the total solution was as follows.
Use Canny Edge Detection with appropriate thresholds to get the outline of objects in the image
Use a Gaussian Blur to Blur the Canny results enough so the flood full wont bleed
Use a flood fill to create the Mask and apply it back to the original image
Some traps for your for young players.
PixelFormats, you need to make sure everything is talking the same format
Not editing the bitmap directly by using scanlines or locked pixels
paralleling algorithms where possible, in this case the flood fill and Blur where good candiates
Update
Even a faster method was just to use Parallel FloodFill with a Color Threshold value
Color Threshold
public static bool IsSimilarColor(this Color source, Color target, int threshold)
{
int r = source.R - target.R, g = source.G - target.G, b = source.B - target.B;
return (r * r + g * g + b * b) <= threshold * threshold;
}
Parallel FloodFill
public static Bitmap ToWhiteCorrection(this Bitmap source, Color sourceColor, Color targetColor, Color maskColor, int threshold, Size tableSize, int cpu = 0)
{
using (var dbMask = new DirectBitmap(source))
{
using (var dbDest = new DirectBitmap(source))
{
var options = new ParallelOptions
{
MaxDegreeOfParallelism = cpu <= 0 ? Environment.ProcessorCount : cpu
};
// Divide the image up
var rects = dbMask.Bounds.GetSubRects(tableSize);
Parallel.ForEach(rects, options, rect => ProcessWhiteCorrection(dbMask, dbDest, rect, sourceColor, targetColor, maskColor, threshold));
return dbDest.CloneBitmap();
}
}
}
private static void ProcessWhiteCorrection(this DirectBitmap dbMask, DirectBitmap dbDest, Rectangle rect, Color sourceColor, Color targetColor, Color maskColor, int threshold)
{
var pixels = new Stack<Point>();
AddStartLocations(dbMask, rect, pixels, sourceColor, threshold);
while (pixels.Count > 0)
{
var point = pixels.Pop();
if (!rect.Contains(point))
{
continue;
}
if (!dbMask[point]
.IsSimilarColor(sourceColor, threshold))
{
continue;
}
dbMask[point] = maskColor;
dbDest[point] = targetColor;
pixels.Push(new Point(point.X - 1, point.Y));
pixels.Push(new Point(point.X + 1, point.Y));
pixels.Push(new Point(point.X, point.Y - 1));
pixels.Push(new Point(point.X, point.Y + 1));
}
}
Worker
private static void ProcessWhiteCorrection(this DirectBitmap dbMask, DirectBitmap dbDest, Rectangle rect, Color sourceColor, Color targetColor, Color maskColor, int threshold)
{
var pixels = new Stack<Point>();
// this basically looks at a 5 by 5 rectangle in all 4 corners of the current rect
// and looks to see if we are all the source color
// basically it just picks good places to start the fill
AddStartLocations(dbMask, rect, pixels, sourceColor, threshold);
while (pixels.Count > 0)
{
var point = pixels.Pop();
if (!rect.Contains(point))
{
continue;
}
if (!dbMask[point].IsSimilarColor(sourceColor, threshold))
{
continue;
}
dbMask[point] = maskColor;
dbDest[point] = targetColor;
pixels.Push(new Point(point.X - 1, point.Y));
pixels.Push(new Point(point.X + 1, point.Y));
pixels.Push(new Point(point.X, point.Y - 1));
pixels.Push(new Point(point.X, point.Y + 1));
}
}
Direct bitmap
public class DirectBitmap : IDisposable
{
public DirectBitmap(int width, int height, PixelFormat pixelFormat = PixelFormat.Format32bppPArgb)
{
Width = width;
Height = height;
Bounds = new Rectangle(0, 0, Width, Height);
Bits = new int[width * height];
BitsHandle = GCHandle.Alloc(Bits, GCHandleType.Pinned);
Bitmap = new Bitmap(width, height, width * 4, PixelFormat.Format32bppPArgb, BitsHandle.AddrOfPinnedObject());
using (var g = Graphics.FromImage(Bitmap))
{
g.Clear(Color.White);
}
}
public DirectBitmap(Bitmap source)
{
Width = source.Width;
Height = source.Height;
Bounds = new Rectangle(0, 0, Width, Height);
Bits = new int[source.Width * source.Height];
BitsHandle = GCHandle.Alloc(Bits, GCHandleType.Pinned);
Stride = (int)GetStride(PixelFormat, Width);
Bitmap = new Bitmap(source.Width, source.Height, Stride, PixelFormat.Format32bppPArgb, BitsHandle.AddrOfPinnedObject());
using (var g = Graphics.FromImage(Bitmap))
{
g.DrawImage(source, new Rectangle(0, 0, source.Width, source.Height));
}
}
...
I've looked everywhere for a workaround to this issue (I may just be blind to see the solutions lying around). My game currently renders the tilemap on the screen and will not render tiles that are not actually within the screen bounds. However, each tile is 16x16 pixels, that means 8100 tiles to draw if every pixel on the screen contains a tile at 1920x1080 resolution.
Drawing that many tiles every cycle really kills my FPS. If I run 800x600 resolution my FPS goes to ~20, and at 1920x1080 it runs at around 3-5 FPS. This really drives me nuts.
I've tried threading and using async tasks, but those just flicker the screen. Probably just me coding it incorrectly.
Here's the drawing code that I currently use.
// Get top-left tile-X
Vector topLeft = new Vector(Screen.Camera.X / 16 - 1,
Screen.Camera.Y / 16 - 1);
Vector bottomRight = new Vector(topLeft.X + (Screen.Width / 16) + 2,
topLeft.Y + (Screen.Height / 16) + 2);
// Iterate sections
foreach (WorldSection section in Sections)
{
// Continue if out of bounds
if (section.X + ((Screen.Width / 16) + 2) < (int)topLeft.X ||
section.X >= bottomRight.X)
continue;
// Draw all tiles within the screen range
for (int x = topLeft.X; x < bottomRight.X; x++)
for (int y = topLeft.Y; y < bottomRight.Y; y++)
if (section.Blocks[x - section.X, y] != '0')
DrawBlock(section.Blocks[x - section.X, y],
x + section.X, y);
}
There are between 8 and 12 sections. Each tile is represented by a char object in the two-dimensional array.
Draw block method:
public void DrawBlock(char block, int x int y)
{
// Get the source rectangle
Rectangle source = new Rectangle(Index(block) % Columns * FrameWidth,
Index(block) / Columns * FrameHeight, FrameWidth, FrameHeight);
// Get position
Vector2 position = new Vector2(x, y);
// Draw the block
Game.spriteBatch.Draw(Frameset, position * new Vector2(FrameWidth, FrameHeight) - Screen.Camera, source, Color.White);
}
The Index() method just returns the frame index of the tile corresponding to the char.
I'm wondering how I could make it possible to actually allow this much to be drawn at once without killing the framerate in this manner. Is the code I provided clearly not very optimized, or is it something specific I should be doing to make it possible to draw this many individual tiles without reducing performance?
Not sure if this is the best way to deal with the problem, but I've started to use RenderTarget2D to pre-render chunks of the world into textures. I have to load chunks within a given area around the actual screen bounds at a time, because loading all chunks at once will make it run out of memory.
When you get close to the bounds of the current pre-rendered area, it will re-process chunks based on your new position in the world. The processing takes roughly 100 milliseconds, so when loading new areas the player will feel a slight slowdown for this duration. I don't really like that, but at least the FPS is 60 now.
Here's my chunk processor:
public bool ProcessChunk(int x, int y)
{
// Create render target
using (RenderTarget2D target = new RenderTarget2D(Game.CurrentDevice, 16 * 48, 16 * 48,
false, SurfaceFormat.Color, DepthFormat.Depth24))
{
// Set render target
Game.CurrentDevice.SetRenderTarget(target);
// Clear back buffer
Game.CurrentDevice.Clear(Color.Black * 0f);
// Begin drawing
Game.spriteBatch.Begin(SpriteSortMode.Texture, BlendState.AlphaBlend);
// Get block coordinates
int bx = x * 48,
by = y * 48;
// Draw blocks
int count = 0;
foreach (WorldSection section in Sections)
{
// Continue if section is out of chunk bounds
if (section.X >= bx + 48) continue;
// Draw all tiles within the screen range
for (int ax = 0; ax < 48; ax++)
for (int ay = 0; ay < 48; ay++)
{
// Get the block character
char b = section.Blocks[ax + bx - section.X, ay + by];
// Draw the block unless it's an empty block
if (b != '0')
{
Processor.Blocks[b.ToString()].DrawBlock(new Vector2(ax, ay), true);
count++;
}
}
}
// End drawing
Game.spriteBatch.End();
// Clear target
target.GraphicsDevice.SetRenderTarget(null);
// Set texture
if (count > 0)
{
// Create texture
Chunks[x, y] = new Texture2D(Game.CurrentDevice, target.Width, target.Height, true, target.Format);
// Set data
Color[] data = new Color[target.Width * target.Height];
target.GetData<Color>(data);
Chunks[x, y].SetData<Color>(data);
// Return true
return true;
}
}
// Return false
return false;
}
If there are any suggestions on how this approach can be improved, I won't be sad to hear them!
Thanks for the help given here!
I'm parsing minecraft file data using Substrate. Minecraft is made up of 'chunks', which are made up of blocks. So I've got a spritesheet with the different 16x16 tiles. I use a croppedbitmap in WPF to find the appropriate graphic tile to make up a section of the map. Then I assign an Image() control in WPF the 'source', which is my croppedbitmap. Finally, I do myCanvas.Children.Add(myImage)... The problem is that after about 30 or so images in my canvas, the application slows to a crawl. This is a problem because typical minecraft maps will have hundreds or thousands of images. Is there a better way to draw a large amount of bitmap data? I like having Image controls because then I can put tooltips on the map and make it interactive. But I accept that an Image control is probably way more expensive and having thousands of them may not work. Here's my code.
(this only parses tiles of a certain value, hence if id == 9)
var mapTiles = (BitmapImage)FindResource("mapTiles");
CroppedBitmap waterImage = new CroppedBitmap(mapTiles, new Int32Rect(352, 48, 16, 16));
CroppedBitmap grassImage = new CroppedBitmap(mapTiles, new Int32Rect(0, 0, 16, 16));
foreach (ChunkRef chunk in chunkManager)
{
countOfTiles++;
for (int x = 0; x <= 15; x++)
{
for (int z = 0; z <= 15; z++)
{
int height = chunk.Blocks.GetHeight(x, z);
//TODO: Normalize Chunk X, Z so 0,0 is the smallest chunk so everything is visible on the canvas.
if (height > 0 && chunk.X > 0 && chunk.Z > 0)
{
var block = chunk.Blocks.GetBlock(x, height - 1, z);
if (block.ID == 9)
{
//352, 48
Image image = new Image();
image.Source = waterImage;
worldMap.Children.Add(image);
Canvas.SetTop(image, (chunk.X + x) * 16);
Canvas.SetLeft(image, (chunk.Z + z) * 16);
image.ToolTip = countOfTiles.ToString();
}
Instead of using the heavier Image, you could use an ImageBrush:
// use ImageBrush's instead
var waterImage = new ImageBrush(new CroppedBitmap(...));
var grassImage = new ImageBrush(new CroppedBitmap(...));
waterImage.Freeze();
grassImage.Freeze();
Later:
if (block.ID == 9)
{
var water = new Rectangle
{
Width = 16,
Height = 16,
Fill = waterImage
};
worldMap.Children.Add(water);
Canvas.SetTop(water, (chunk.X + x) * 16);
Canvas.SetLeft(water, (chunk.Z + z) * 16);
water.ToolTip = countOfTiles.ToString();
}
If I leave out Freeze, I can't get a test app with a 100x100 tile world to display properly. When I freeze each of the brushes, I can get the map to display seamlessly up to about 300x300. At 1000x1000 it almost does not load, but at this point you need to consider virtualizing the view as the UI won't handle that many UIElement's gracefully.
As title said:
I have form with 2 trackbars. One for frequency and one for amplitude. I set up timer for on-the-fly changing.
private void timer1_Tick(object sender, EventArgs e)
{
float amplitude, frequency;
amplitude = Convert.ToSingle(trackBar1.Value) / 100;
label1.Text = amplitude.ToString() + " V";
frequency = trackBar2.Value;
label2.Text = frequency.ToString() + " Hz";
}
I have also 4 radio-buttons to decide, which type of signal will be displayed (sine, square, triangle, sawthoot)
Now I have this implemented with ImageList (change image of signal).
How can I draw type of signal and regulate it with with trackbars? So it will be like in osciloscope.
Thanks for your answers and code.
Lets start by creating the different signal types, this is a function that creates one wavelength of amplitude 1:
private PointF[] CreateBaseSignal(SignalType signalType)
{
switch (signalType)
{
case SignalType.Sine:
const int oversampling = 32;
PointF[] signal = new PointF[oversampling];
for (int i = 0; i < signal.Length; i++)
{
signal[i].X = (float) i / oversampling;
signal[i].Y = Convert.ToSingle(Math.Sin((double) i / oversampling * 2 * Math.PI));
}
return signal;
case SignalType.Square:
return new PointF[]
{
new PointF(0.0f, -1.0f),
new PointF(0.5f, -1.0f),
new PointF(0.5f, 1.0f),
new PointF(1.0f, 1.0f),
};
case SignalType.Triangle:
return new PointF[]
{
new PointF(0.0f, -1.0f),
new PointF(0.5f, 1.0f),
};
case SignalType.Sawtooth:
return new PointF[]
{
new PointF(0.0f, -1.0f),
new PointF(1.0f, 1.0f),
};
default:
throw new ArgumentException("Invalid signal type", "signalType");
}
}
Then we create the actual signal with the selected amplitude and frequency:
private PointF[] CreateSignal(PointF[] baseSignal, float frequency, float amplitude)
{
PointF[] signal = new PointF[Convert.ToInt32(Math.Ceiling(baseSignal.Length * frequency))];
for(int i = 0; i < signal.Length; i++)
{
signal[i].X = baseSignal[i % baseSignal.Length].X / frequency + (i / baseSignal.Length) / frequency;
signal[i].Y = baseSignal[i % baseSignal.Length].Y * amplitude;
}
return signal;
}
Before attempting to plot this signal to a PictureBox, we scale the signal to fit the width and height:
private PointF[] ScaleSignal(PointF[] signal, int width, int height)
{
const float maximumAmplitude = 10.0f;
PointF[] scaledSignal = new PointF[signal.Length];
for(int i = 0; i < signal.Length; i++)
{
scaledSignal[i].X = signal[i].X * width;
scaledSignal[i].Y = signal[i].Y * height / 2 / maximumAmplitude;
}
return scaledSignal;
}
Using Graphics.DrawLine to plot the signal is way better than Bitmap.SetPixel, since the data points will be connected even at high frequencies. Bitmap.SetPixel is also very slow, you really need to use Bitmap.LockBits and unsafe code for manipulating single pixels to achieve any decent performance. Using Graphics.DrawLine, you also have control over line width, anti-aliasing etc.
Since we have stored the signal in a PointF array, we can use the simple Graphics.DrawLines method to plot the signal instead of iterating over the data points:
private void PlotSignal(PointF[] signal, PictureBox pictureBox)
{
Bitmap bmp = new Bitmap(pictureBox.ClientSize.Width, pictureBox.ClientSize.Height);
signal = ScaleSignal(signal, bmp.Width, bmp.Height); // Scale signal to fit image
using(Graphics gfx = Graphics.FromImage(bmp))
{
gfx.SmoothingMode = SmoothingMode.HighQuality;
gfx.TranslateTransform(0, bmp.Height / 2); // Move Y=0 to center of image
gfx.ScaleTransform(1, -1); // Make positive Y axis point upward
gfx.DrawLine(Pens.Black, 0, 0, bmp.Width, 0); // Draw zero axis
gfx.DrawLines(Pens.Blue, signal); // Draw signal
}
// Make sure the bitmap is disposed the next time around
Image old = pictureBox.Image;
pictureBox.Image = bmp;
if(old != null)
old.Dispose();
}
If you redraw the signal often, you probably want to reuse the the Bitmap and Graphics objects instead of creating new ones each time. Just remember to call Graphics.Clear between each redraw.
Putting everything together in one big statement:
PlotSignal(
CreateSignal(
CreateBaseSignal(signalType),
frequency,
amplitude),
thePictureBox);
If you're after a fast plotting library, I really like Dynamic Data Display
Dynamic Data Display
This is a WPF component, but for fast, smooth drawing applications I really think it is worthwhile to port to WPF sooner rathar than later. It feels like you're not too far into your project at the moment anyway.
Development for WPF seems to have stopped for this component (although it continues to be worked on for Silverlight). The documentation is terrible but the source code is available from the link above so you can extend it as needed (it's quite well written and very extensible) and the source is invaluable as a substitute for the near complete lack of any documentation.
Assuming you want to plot a sin wave on a picture box control, create a picture box control on your form then perform the following:
int width = pictureBox1.Width;
int height = pictureBox1.Height;
Bitmap b = new Bitmap(width, height);
for (int i = 0; i < width; i++)
{
int y = (int)((Math.Sin((double)i * 2.0 * Math.PI / width) + 1.0) * (height - 1) / 2.0);
b.SetPixel(i, y, System.Drawing.Color.Red);
}
pictureBox1.Image = b;
I have small function which will recolor pixels in a Bitmap from a given color to a new given color.
The problems I have with the code are as follows:
1)
The function gives results which are remapping white pixels which should not be concidered since I have a threshold... (unless I have defined this calculation wrong)
2) When certain colors are given e.g. LimeGreen wierd results are seen in the image returned from the function (I beleive this is due to overflow of the byte type in the addition or subtraction case)
The base image I am using can be found here:
http://www.freeimagehosting.net/uploads/c8745a9de1.png
Results I have obtained can be found here:
freeimagehosting.net/uploads/fa48e5a0eb.png (Called with Color.Magenta as remapColor, Color.Red as newColor, Seems like white pixels are effected and the end of the gradient is not colored correctly)
freeimagehosting.net/uploads/8faec6a569.png (Called with Color.Magenta as remapColor, Color.Yellow as newColor, Seems like white pixels are effected and the end of the gradient is not colored correctly)
freeimagehosting.net/uploads/2efd4c04aa.png (Called with Color.Magenta as remapColor, Color.Blue as newColor, Seems like gradient not colored correctly)
freeimagehosting.net/uploads/defdf04e16.png (Called with Color.Magenta as remapColor, Color.Teal as newColor, Seems like white pixels are effected and none of the gradient is calculated correctly)
The function I have for this code is below: UPDATED per suggestions
public unsafe static Bitmap RecolorImage(Bitmap original, Color remapColor, Color newColor)
{
Bitmap result = new Bitmap(original.Width, original.Height);
//lock the original bitmap in memory
BitmapData originalData = original.LockBits(
new Rectangle(0, 0, original.Width, original.Height),
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
//lock the new bitmap in memory
BitmapData newData = result.LockBits(
new Rectangle(0, 0, original.Width, original.Height),
ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
//set the number of bytes per pixel
int pixelSize = 4;
int rthreshold = 128;
int gthreshold = 128;
int bthreshold = 128;
for (int y = 0; y < original.Height; y++)
{
//get the data from the original image
byte* oRow = (byte*)originalData.Scan0 + (y * originalData.Stride);
//get the data from the new image
byte* nRow = (byte*)newData.Scan0 + (y * newData.Stride);
for (int x = 0; x < original.Width; x++)
{
//examine the rgb values
byte r = (byte)((oRow[x * pixelSize]));
byte g = (byte)((oRow[x * pixelSize + 1]));
byte b = (byte)((oRow[x * pixelSize + 2]));
byte a = (byte)((oRow[x * pixelSize + 3]));
if (a > 0 &&
Math.Abs(remapColor.R - r) <= rthreshold &&
Math.Abs(remapColor.B - b) <= bthreshold &&
Math.Abs(remapColor.G - g) <= gthreshold
)
{
if (newColor.R == 0)
{
r = 0;
}
else
{
if (newColor.R > remapColor.R)
r = (byte)(r - newColor.R);
else
r = (byte)(r + newColor.R);
}
if (newColor.G == 0)
{
g = 0;
}
else
{
if (newColor.G > remapColor.G)
g = (byte)(g - newColor.G);
else
g = (byte)(g + newColor.G);
}
if (newColor.B == 0)
{
b = 0;
}
else
{
if (newColor.B > remapColor.B)
b = (byte)(b - newColor.B);
else
b = (byte)(b + newColor.B);
}
}
//set the new image's pixel remaped pixel color
nRow[x * pixelSize] = b; //B
nRow[x * pixelSize + 1] = g; //G
nRow[x * pixelSize + 2] = r; //R
nRow[x * pixelSize + 3] = a; //A
}
}
original.UnlockBits(originalData);
result.UnlockBits(newData);
return result;
}
What gives....
Is what I am trying to do possible?
Is it reliable?
Is there just a bug in my code?
Is there a better way to achive this "re-mapable technique" on bitmaps using gradients?
Thank you for your time.
It looks like your threshold test is incorrect. Take the line:
remapColor.R - r <= rthreshold
If the current pixel is white, then r will be 255, and the test will always be true, no matter what remapColor.R and rthreshold are.
I think Math.Abs(remapColor.R - r) might work.
And you're likely correct about your byte values being out of bounds. Fixing the threshold test might stop that from happening. Otherwise, try putting some bounds checking in to see where it's happening.
I have decided that although this may be possible if I study the various materials regarding color spaces and their supporting theories. It seems that this will take a bit more than some quick threshold calculation and normalization to the remapColor.
I am going to propose that instead of performing this type of modification on a raster bitmap image that the graphics be modified in their vector form.
The process should be something like this:
The graphics are created in whatever imaging suite the designer is working in.
They are saved to a vector format e.g. SVG this will allow the customizable paths to be named, traversed and altered programmatically (and for more than color if needed) with SVG Rendering Engine(http://svg.codeplex.com/)
With this solution we can either output the SVG direct to the browser if supported and do the modifications directly on the client or use the server and output as PNG when needed.
I feel that this arrangement will provide us with more flexibility and a more robust solution than what I was initially going to hack together.
Thank you guys for your time!