So I'm no expert in coding: I just have a vague understanding of a few bits and bobs.
I understand that images of pixel dimensions X*Y are stored in array of size 3*X*Y where each vector pulled from a given (x,y) value has 3 elements for the 3 RGB values.
I also understand that one can also store an image in a 4*X*Y array, where each pulled vector now has 4 values, RGBA with A being Alpha, used to represent the opacity of a particular pixel.
Now I'm into animation, and have pencil drawings of white clouds on a black background that I want to import into Flash. I would like the blacker parts of the drawing to be more transparent and the lighter parts to be more opaque. I have the scans of the drawings saved in .png format.
If I had any idea how to manipulate an image at the 'array level', I could have a stab at this myself but I'm at a loss.
I need a program that, given a .png image and a reference RGB value {a b c}, obtains the RGB array of the image and converts it into an RBGA array such that:
a pixel of RGB value {p q r}
...... Becomes ......
a pixel of RGBA value {p q r 1-M[(|p-a|^2 + |q-b|^2 + |r-c|^2)^1/2]}.
Where M is a normalisation factor which makes the largest alpha value = 1.
i.e. M = 1/[(255^2 + 255^2 + 255^2)^1/2]) = 0.0026411...
i.e. the alpha value of the replacement pixel is the 'distance' between the colour of the pixel and some reference colour which can be input.
This then needs to export the new RGBA Array as a png image.
Any ideas or any fellow animators know if this can be done directly with actionscript?
Example: Reference = {250 251 245}
RGB array =
|{250 251 245} {250 250 250}|
|{30 255 22} {234 250 0 }|
...... Becomes ......
RGBA array =
|{250 251 245 1} {250 251 245 0.987}|
|{30 255 22 0.173} {234 250 0 0.352}|
You can do this quite simply, just at the command-line, with ImageMagick which is installed on most Linux distros and is available for free on OSX and Windows.
The "secret sauce" is the -fx operator - described here.
So, let's generate a 300x200 black image and then use -fx to calculate the red channel so that the red varies across the image according to what fraction of the width (w) we are from the left side (i):
convert -size 300x200 xc:black -channel R -fx 'i/w' result.png
Note that I am generating an image "on-the-fly" with -size 300x200 xc:black, whereas if you have a PNG file with your animation frame in it, you can put that in, in its place.
Now let's say we want to vary the opacity/alpha too - according to the distance down the image from the top:
convert -size 300x200 xc:black -alpha on \
-channel R -fx 'i/w' \
-channel A -fx 'j/h' result.png
Ok, we are getting there... your function is a bit more complicated, so, rather than typing it on the command-line every time, we can put it in a script file called RGB2Opacity.fx like this:
convert -size 300x200 xc:black -alpha on -channel A -fx #RGB2Opacity.fx result.png
where RGB2Opacity.fx is simple and looks like this for the moment:
0.2
Now we need to put your "reference" pixel on the command line with your animation frame so that ImageMagick can work out the difference. That means your actual command-line will look more or less exactly like the following:
convert -size 300x200 xc:"rgb(250,251,245)" YourImage.png -alpha on -channel A -fx #RGB2Opacity.fx result.png
And then we need to implement your formula in the -fx script file. Your variable names must be at least 2 letters long with no digits in them, and you should return a single value for the opacity. Variables are all scaled between [0,1.0] so your 255 scaling is a little different. I have no sample image and I am not sure how the answer is supposed to look, but it will be pretty close to this:
MM=1/pow(3,0.5);
pmasq=pow((u.r-v.r),2.0);
qmbsq=pow((u.g-v.g),2.0);
rmcsq=pow((u.b-v.b),2.0);
1-MM*pow((pmasq+qmbsq+rmcsq),0.5)
I don't know if/how to put comments in -fx scripts, so I will explain the variable names below:
pmasq is the square of p minus a.
qmbsq is the square of q minus b.
rmcsq is the square of r minus c.
u.r refers to the red channel of the first image in ImageMagick's list, i.e. the red channel of your reference pixel.
v.g refers to the green channel of the second image in ImageMagick's list, i.e. the green channel of your animation frame.
Let's create your animation frame now:
convert xc:"rgb(250,251,245)" xc:"rgb(250,250,250)" xc:"rgb(30,255,22)" xc:"rgb(234,250,0)" +append frame.png
And check it looks correct:
convert frame.png txt:
Output
# ImageMagick pixel enumeration: 4,1,65535,srgb
0,0: (64250,64507,62965) #FAFBF5 srgb(250,251,245)
1,0: (64250,64250,64250) #FAFAFA grey98
2,0: (7710,65535,5654) #1EFF16 srgb(30,255,22)
3,0: (60138,64250,0) #EAFA00 srgb(234,250,0)
If we apply that to your image and check the results, you can see I have got it slightly wrong somewhere, butI'll leave you (or some other bright spark) to work that out...
convert -size 4x1 xc:"rgb(250,251,245)" frame.png -alpha on -channel A -fx #RGB2Opacity.fx result.png
convert result.png txt:
# ImageMagick pixel enumeration: 4,1,65535,srgba
0,0: (64250,64507,62965,65535) #FAFBF5FF srgba(250,251,245,1)
1,0: (64250,64507,62965,64764) #FAFBF5FC srgba(250,251,245,0.988235)
2,0: (64250,64507,62965,19018) #FAFBF54A srgba(250,251,245,0.290196)
3,0: (64250,64507,62965,29041) #FAFBF571 srgba(250,251,245,0.443137)
Related
I have an image that I import into Octave 5.2, and I would like to create an outline all the way around the image array using RGB values.
I'm having trouble inserting the RGB values back into the array correctly, inserting / generating the two rows at top, two columns on the left, two columns on the right, and two rows on the bottom all the way around the image / converted double array.
Example of original image:
Example of the image I'm trying to get when done:
My logic was:
To convert the image to a double array so I can do math / insert the RGB values where I wanted them.
Insert the RGB values into the left, right, top, bottom of the array.
Convert the double array back to uint8 to export / view it as image.
My code so far:
pkg load image
img_fn=('https://i.imgur.com/KKxJaOy.png'); %original image
f=imread(img_fn);
[im_r im_c]=size(f);
size_min=min(im_r,im_c); %get minum size from row and col
f_double=double(f); %need to convert to double to do math functions on it
outline_left_of_box=repmat(255,[rows(f_double),2]); %Create left line array of outline red box
f_double_tmp_red(:,:,1)=[outline_left_of_box,f_double];%Create left line of outline red box
red_outline_right_of_box=repmat(255,[rows(f_double),2]); %Create right line array of outline red box
red_outline_top_of_box=repmat(255,[2,columns(f_double)]); %Create top line array of outline red box
red_outline_bottom_of_box=repmat(255,[2,columns(f_double)]); %Create bottom line array of outline red box
%convert back to image
red_outline_img=uint8(f_double);
imshow(red_outline_img); %used to show image in octave plot window
Please note: I'm converting the image array into a double because calculations will be done on the array to get the desired color box around the image, but I'm just trying to get the inserting RGB values into the array issue fixed first.
Maybe it's easier to simply paste the inner part of the input image onto some "background" image with the desired border color, like so:
pkg load image
% Read image, get dimensions, convert to double
f = imread('https://i.imgur.com/KKxJaOy.png');
[im_ro, im_co, im_ch] = size(f);
f_double = double(f);
% Set up width and color of border
bw = 2;
color = ones(1, 1, im_ch);
color(1, 1, :) = [255, 0, 0];
% Create image of same size as input with solid color, and paste inner part of input
red_outline_img = ones(im_ro, im_co, im_ch) .* color;
red_outline_img(bw+1:end-bw, bw+1:end-bw, :) = f_double(bw+1:end-bw, bw+1:end-bw, :);
red_outline_img = uint8(red_outline_img);
imshow(red_outline_img);
That'd be the output:
Another thing you could try is plot the lines as you suggest, which can be done very efficiently with some clever use of xlim/ylim, and then print the whole thing as an -RGBImage to get it back in image form.
The only caveat here though is that you will need to play with the formatting options to get what you're really after (e.g. in case you want higher or lower resolution), since you really are printing what's on your screen at this point.
E.g.
L = imread('leaf.png');
imshow(L)
hold on
plot( [xlim, fliplr(xlim);xlim, xlim], [ylim, ylim;fliplr(ylim), ylim], 'r', 'linewidth', 2 )
hold off
set( gca, 'position', [0, 0, 1, 1] );
set( gcf, 'paperposition', [0, 0, 1, 1] );
R = print( '-RGBImage' );
close
imshow(R); set( gcf, 'color', 'k'); axis off
I am trying to figure out how to load a PNG image and create a matrix containing the RGB values of each pixel. I am currently using the following method to load the file and get the various RGB values:
def to_pixels
File.open(#file, 'r') do |file|
byte_block = file.read
byte_block.each_byte do |byte|
#pixels << byte
end
end
end
From my understanding, each pixel contains 3-bytes representing the R,G, and B value. I initially tried taking the output array #pixels and grouping into sub-groups of 3 elements assuming that the pixel order and RGB value of each pixel was preserved in my output. E.g.:
#pixels = #pixels.each_slice(3).to_a
The length of the array that I created was nearly the same length as the total number of pixels in my original image, so I was encouraged. However, I used ChunkyPNG to take my RGB pixel array and print back to an image, and it looks like random color noise. Could some of the bytes being input into #pixels represent metadata? Or perhaps would the bytes being output not be ordered as R,G, then B values of individual pictures, but perhaps all the R bytes, then all the G bytes, then all the B bytes for example?
I would like to figure out how to transform the byte array into an array of arrays grouping RGB values of the image in some logical order (start at top left and work across to the right, or start in top left and work down, etc)
The chunky_png gem can do this. https://github.com/wvanbergen/chunky_png
Something like:
img = ChunkyPNG::Image.from_file(#file)
img.pixels.each do |pixel|
puts ChunkyPNG::Color.to_hex(pixel) # would spit out a hex string like "#b8e1f6ff"
end
There are a number of other methods if you want different formats: to_grayscale, to_grayscale_alpha_bytes, to_grayscale_bytes, to_hex, to_hsb, to_hsl, to_hsv, to_s, to_truecolor_alpha_bytes, to_truecolor_bytes.
I have a cell array called output. Output contains matrices of size 1024 x 1024, type = double, grayscale. I would like to plot each matrix and its corresponding histogram on a single plot. Here is what I have so far:
for i = 1:size(output,2)
figure
subplot(2,1,1)
imagesc(output{1,i});
colormap('gray')
colorbar;
title(num2str(dinfo(i).name))
subplot(2,1,2)
[pixelCount, grayLevels] = imhist(output{1,i});
bar(pixelCount);
title('Histogram of original image');
xlim([0 grayLevels(end)]); % Scale x axis manually.
grid on;
end
The plot I get, however, seems to be faulty... I was expecting a distribution of bars.
I am somewhat lost at how to proceed, any help or suggestions would be appreciated!
Thanks :)
Based on the colorbar on your image plot the values of your image pixels range from [0, 5*10^6].
For many image processing functions, MATLAB assumes one of two color models, double values ranging from [0, 1] or integer values ranging from [0 255]. While the supported ranges are not explicitly mentioned in the imhist documentation, in the "Tips" section of the imhist documentation, there is a table of scale factors for different numeric types that hints at these assumptions.
I think the discrepancy between your image range and these models is the root of the problem.
For example, I load a grayscale image and scale the pixels by 1000 to approximate your data.
% Toy data to approximate your image
I = im2double(imread('cameraman.tif'));
output = {I, I .* 1000};
for i = 1:size(output,2)
figure
subplot(2,1,1)
imagesc(output{1,i});
colormap('gray')
colorbar;
subplot(2,1,2)
[pixelCount, grayLevels] = imhist(output{1,i});
bar(pixelCount);
title('Histogram of original image');
grid on;
end
The first image is using a matrix with the standard [0,1] double value range. The imhist calculates a histogram as expected. The second image is using a matrix with the scaled [0, 1000] double value range. imhist assigns all the pixels to the 255 bin since that is the maximum bin. Therefore, we need a method that allows us to scale the bins.
Solution : Use histogram
histogram is designed for any numeric type and range. You may need to fiddle with the bin edges to show the structures that you are interested in as it doesn't initialize bins the same way imhist does.
figure
subplot(2,1,1)
imagesc(output{1,2});
colormap('gray')
colorbar;
subplot(2,1,2)
histogram(output{1,2});
title('Histogram of original image');
grid on;
Say you want to put pixel with a color (R0 G200 B255) in a BMP picture and you have transp option in percents.
How do I determine the new pixel color, considering the transp and the background color?
I actually could figure out a formula that looks promising:
newpixel = newpixel + (bgpixel * transp) / %(transp of 255)
I created it by analyzing the pixel color change in GIMP. Not sure if that is the correct formula. I think it is also rounded up.
The standard formula is pixel = new_pixel * alpha + pixel * (1 - alpha), where alpha is a number between 0 and 1 that describes the opacity of the new (foreground) pixel.
You'll note that if the new pixel is fully transparent (alpha = 0) the pixel is unchanged and that if the new pixel is fully opaque (alpha = 1) the new pixel replaces the old one.
This formula must be applied separately for each pixel components (red, green and blue).
the code needs to change to 24 bit pixels.
where the 4th byte is the transparency factor.
The actual color values do not change
I've color values coming in six bits, with two bits each for red, green and blue. So black would be represented as binary 000000, red 110000, blue 000011, yellow 111100 and so on.
I have to convert this color into to 24 bit rgb value to pass it to the graphics layer (DirectFB). Since three (binary 11) is to become 255 (0xFF), I used the following formula with 85 (=255/3) as the conversion factor.
r = (color_6bit >> 4) * FACTOR;
g = ((color_6bit >> 2) & 0x3) * FACTOR;
b = (color_6bit & 0x3) * FACTOR;
color_32bit = (r << 16)| (g << 8) | b;
This correctly converts the colors (white [0x3F -> 0xFFFFFF], red [0x30 -> 0xFF0000] and so on).
Now, these colors are text and background colors of captions to be displayed on TV and we have test streams that have reference color palette embedded in the video. When I draw the eight bit colors obtained using this formula on to the screen, it is not exactly matching with the reference color present in the video - it is fairly close, but there is a difference.
Am I doing the conversion correctly or is there any standard algorithm for converting two bit rgb color values to eight bit rgb values? Could DirectFB be using some different representation (like RGB565) internally?
For what it is worth, when the factor 85 is replaced with 48 (value found by trial and error), the colors are matching almost perfectly.
The only standard I know of is EGA - there's a reference on wikipedia.
For what it's worth, 6 bits is a very small space - only 64 values. The quickest way of doing the conversion in terms of both cpu time and memory is almost certainly just looking up the value in an array of size 64. Especially if you have test data, this is really easy - just put the correct 64 values from the test data in the array. That way you don't need to worry if it is a standard - the standard is exactly whatever works for this system.