Programatically How do I reset colors in PDF - pdf-generation

I am creating a PDF file and I would like to generate several rectangles on the page
However once I fill a rectangle in color I cannot reset the fill color (non-stroking color) to
clear, white, transparent or whatever is needed so that no color is in the rectangle
I use this to set the stroke and non-stroke to red
1.0 0.0 0.0 rg
1.0 0.0 0.0 RG
I set the line width
1 w
I draw a rectangle and it appears as a red rectangle (outlined and filled in red)
0046.8 0633.6 0237.6 0100.8 re
B
Now I set the stroke to black
0.0 0.0 0.0 RG
I attempt to create another rectangle (I only want black outline)
0072.0 0576.0 0288.0 0144.0 re
B
But the second rectangle appears with a black outline but is filled in red?
How do I get red of the red fill?

Your problem is your use of B. If you interchange the two drawing operations you'll see that (what was) the second rectangle is now filled with black, because black is the default color. You should use S for the second rectangle, so that that path is stroked but not filled.
By the way, you're missing some really easy ways to reduce the size of your generated PDF:
Use g/G instead of rg/RG when all three numbers are the same.
1 w is unnecessary (unless returning to that setting from a different one), 1 is the default value for stroke width.
Don't print trailing .0 or leading zeroes on your numbers.
Putting all which together, here's what your generated page stream should look like:
1 0 0 rg 1 0 0 RG
46.8 633.6 237.6 100.8 re B
0 G
72 576 288 144 re S

Related

OpenCV line detection for 45 degree lines

I have an image:
In this image, the OpenCV Hough transform can't detect the big -45 degree line using
minLineLength = 150
maxLineGap = 5
line_thr = 150
linesP = cv.HoughLinesP(dst, 1, np.pi / 180, line_thr, None, minLineLength, maxLineGap)
The only lines found are:
I tried playing with various thresholds also but I can't find the line here.
If I manually crop the image like this:
then I can clearly see the OpenCV Hough transform finding the right line:
I want to find this same line in the non cropped version. Any suggestions on the non-cropped version to find it?
Also there can be cases where there is no line at all or the line doesn't go all the way for X-axis length.
Examples
I implemented a slightly simpler algorithm than my other answer but in Python with OpenCV this time.
Basically, rather than taking the mean of vertical columns of pixels, it sums the pixels in the columns and chooses the column that is brightest. If I show the padded, rotated image with another image below representing the sums of the columns, you should see how it works:
#!/usr/bin/env python3
import cv2
import numpy as np
# Load image as greyscale
im = cv2.imread('45.jpg',cv2.IMREAD_GRAYSCALE)
# Pad with border so it isn't cropped when rotated
bw=300
bordered = cv2.copyMakeBorder(im, top=bw, bottom=bw, left=bw, right=bw, borderType= cv2.BORDER_CONSTANT)
# Rotate -45 degrees
w, h = bordered.shape
M = cv2.getRotationMatrix2D((h/2,w/2),-45,1)
paddedrotated = cv2.warpAffine(bordered,M,(h,w))
# DEBUG cv2.imwrite('1.tif',paddedrotated)
# Sum the elements of each column and find column with most white pixels
colsum = np.sum(paddedrotated,axis=0,dtype=np.float)
col = np.argmax(colsum)
# DEBUG cv2.imwrite('2.tif',colsum)
# Fill with black except for the line we have located which we make white
paddedrotated[:,:] = 0
paddedrotated[:,col] = 255
# Rotate back to straight
w, h = paddedrotated.shape
M = cv2.getRotationMatrix2D((h/2,w/2),45,1)
straight = cv2.warpAffine(paddedrotated,M,(h,w))
# Remove padding and save to disk
straight = straight[bw:-bw,bw:-bw]
cv2.imwrite('result.png',straight)
Note that you don't actually have to rotate the image back to straight and crop it back to its original size. You could actually stop after the first line that says:
col = np.argmax(colsum)
and use some elementary trigonometry to work out what that means in your original image.
Here is the output:
Keywords: line detection, detect line, rotate, pad, border, projection, project, image, image processing, Python, OpenCV, affine, Hough
I did this on the command-line in Terminal with ImageMagick but you can apply exactly the same technique with OpenCV.
Step 1
Take the image and rotate it 45 degrees introducing black pixels as background where required:
convert 45.jpg -background black -rotate 45 result.png
Step 2
Now, building on the previous command, set every pixel to the median of the box 1px wide and 250px tall centred on it:
convert 45.jpg -background black -rotate 45 -statistic median 1x250 result.png
Step 3
Now, again building on the previous command, rotate it back 45 degrees:
convert 45.jpg -background black -rotate 45 -statistic median 1x250 -rotate -45 result.png
So, in summary, the entire processing is:
convert input.jpg -background black -rotate 45 -statistic median 1x250 -rotate -45 result.png
Obviously then crop it back to the original size and append side-by-side with the original for checking:
convert 45.jpg -background black -rotate 45 -statistic median 5x250 -rotate -45 +repage -gravity center -crop 184x866+0+0 result.png
convert 45.jpg result.png +append result.png
You can also use mean statistic plus thresholding rather than median since it is quicker than sorting to find the median, however it tends to lead to smearing:
convert 45.jpg -background black -rotate 45 -statistic mean 1x250 result.png
Your newly-added image gets processed to this result:
The problem is clearly that the line you are searching for is not a line. It looks actually like a train of connected circles and boxes. Therefore, I recommend that you do the following:
Find all contours in the image using find contours
img = cv.imread('image.jpg')
img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(img_gray, 127, 255, 0)
img2, contours, hierarchy = cv.findContours(thresh, CHAIN_APPROX_SIMPLE ,cv.RETR_EXTERNAL)
This will return many many contours, so use a loop to save only long enough contours. Since the image size is 814x1041 pixels, I assume the contour long if it is at least 10% of the image width which is almost 100 (you must apparently optimize this value)
long_contours = []
for contour in contours[i]:
perimeter = cv2.arcLength(contour,True)
if (perimeter > 0.1 * 1018) # 10% of the image width
long_contours.append(contour)
Now draw a rotated bounding rectangle around those long contours that might be a line as well. The long contour is considered a line if its width is much longer than its height, or its aspect ratio is large (such as 8, and you need also to optimize this value)
for long_contour in long_contours:
rect = cv2.minAreaRect(long_contour)
aspec_ratio = rect.width / rect.height
if aspec_ratio > 8 :
box = cv2.boxPoints(rect)
box = np.int0(box)
cv2.drawContours(img,[box],0,(255,255,255),cv.FILLED)
Finally you should get something like that. Please note the code here is for guidance only.
Your original code is fine as a whistle. The only problem is that your image contains too many information which mess up the accumulator scores. Everything will work out if you increase the line threshold to 255.
minLineLength = 150
maxLineGap = 5
line_thr = 255
linesP = cv2.HoughLinesP(dst, 1, np.pi / 180.0, line_thr, None, minLineLength, maxLineGap)
Here are the results using that value.
3 lines are detected here due to the large white pixel size.
[ 1 41 286 326]
[ 0 42 208 250]
[ 1 42 286 327]
5 lines are detected around the same area due to the same reason as above. Reducing the pixel sizes using morphological operation or distance transform should fix this.
[110 392 121 598]
[112 393 119 544]
[141 567 147 416]
[ 29 263 29 112]
[ 0 93 179 272]
No line found here.

Turn black pixel on the first image to white if it is also black on a second co-located image

I have 2 co-located images, both created in a similar way and both have the size of 7,221 x 119 pixels.
I want to write a logic like this:
If the R,G,B values of a certain pixel (called it x) in image 1 = 0,0,0 (black) And the R,G,B values of pixel x in image 2 = 0,0,0 (black) then change the R,G,B values of pixel x in image 1 to 255,255,255 (white), Else no change.
How can I do this in either Matlab or Python?
You should be able to do this in python with the Pillow package. You need to load the two pixels, check if all the color channels are 0 and if so make them 255, then save the image again. In Python 0 is interpreted as False, so not any(vals) will be True when vals includes only zeros.
from PIL import Image
im1 = Image.open("image1.jpg")
im2 = Image.open("image2.jpg")
pixel = (0, 0)
newcolor = (255,)*3
if not any(im1.getpixel(pixel)) and not any(im2.getpixel(pixel)):
im1.putpixel(pixel, newcolor)
im1.save('image1conv.jpg')
Note that not any(im1.getpixel(pixel)) and not any(im2.getpixel(pixel)) could be rewritten as not any(im1.getpixel(pixel) + im2.getpixel(pixel)), but I think the first way has clearer logic.

How to programmatically generate a ring/annulus with varying blocks of colors

A picture is worth a thousand words...
I'd like to know how to be able to generate an image like that where (1) the two circles are obviously perfect circles, (2) I can define the beginning and ending parts of each region in terms of angles, e.g. section 1 starts at 0 radians from the vertical and ends at pi/2 radians from the vertical, etc., and (3) I can define the color of each region.
In fact the outside and inside of the ring should not have a black border; the border of each region should be the same color as each region.
How might I do this with, say, ImageMagick?
You can create annulus in vector graphics with the Arc command. See Mozilla's Path Document for details on parameter.
With ImageMagick, you could -draw any part of an vector-graphic. Example:
convert -size 100x100 xc:transparent -stroke black -strokewidth 1 \
-fill blue -draw 'path "M 10 50 A 40 40 0 0 1 50 10 L 50 20 A 30 30 0 0 0 20 50 Z"' \
-fill red -draw 'path "M 50 10 A 40 40 0 0 1 90 50 L 80 50 A 30 30 0 0 0 50 20 Z"' \
-fill green -draw 'path "M 90 50 A 40 40 0 0 1 10 50 L 20 50 A 30 30 0 0 0 80 50 Z"' \
annulus.png
Other great example here, and here
update
To create a more programmatic approach, use any OOP scripting language. Below is quick example with Python & Wand, but Ruby & RMagick are also highly recommended.
#!/usr/bin/env python3
import math
from wand.color import Color
from wand.drawing import Drawing
from wand.image import Image
class Annulus(Image):
def __init__(self, inner, outer, padding=5):
self.Ri = inner
self.Ro = outer
side = (outer + padding) * 2
self.midpoint = side/2
super(Annulus, self).__init__(width=side,
height=side,
background=Color("transparent"))
self.format = 'PNG'
def __iadd__(self, segment):
cos_start, cos_end = math.cos(segment.As), math.cos(segment.Ae)
sin_start, sin_end = math.sin(segment.As), math.sin(segment.Ae)
SiX, SiY = self.midpoint + self.Ri * cos_start, self.midpoint + self.Ri * sin_start
SoX, SoY = self.midpoint + self.Ro * cos_start, self.midpoint + self.Ro * sin_start
EiX, EiY = self.midpoint + self.Ri * cos_end, self.midpoint + self.Ri * sin_end
EoX, EoY = self.midpoint + self.Ro * cos_end, self.midpoint + self.Ro * sin_end
with Drawing() as draw:
for key, value in segment.draw_args.items():
setattr(draw, key, value)
draw.path_start()
draw.path_move(to=(SiX, SiY))
draw.path_elliptic_arc(to=(EiX, EiY),
radius=(self.Ri, self.Ri),
clockwise=True)
draw.path_line(to=(EoX, EoY))
draw.path_elliptic_arc(to=(SoX, SoY),
radius=(self.Ro, self.Ro),
clockwise=False)
draw.path_close()
draw.path_finish()
draw(self)
return self
class Segment(object):
def __init__(self, start=0.0, end=0.0, **kwargs):
self.As = start
self.Ae = end
self.draw_args = kwargs
if __name__ == '__main__':
from wand.display import display
ring = Annulus(20, 40)
ring += Segment(start=0,
end=math.pi/2,
fill_color=Color("yellow"))
ring += Segment(start=math.pi/2,
end=math.pi,
fill_color=Color("pink"),
stroke_color=Color("magenta"),
stroke_width=1)
ring += Segment(start=math.pi,
end=0,
fill_color=Color("lime"),
stroke_color=Color("orange"),
stroke_width=4)
display(ring)
I know little about gnuplot but think it probaby fits the bill here - my commands may be crude, but they seem pretty legible and effective. Someone cleverer than me may be able to improve them!
Anyway, here is the script I came up with:
set xrange [-1:1]
set yrange [-1:1]
set angles degrees
set size ratio -1
# r1 = annulus outer radius, r2 = annulus inner radius
r1=1.0
r2=0.8
unset border; unset tics; unset key; unset raxis
set terminal png size 1000,1000
set output 'output.png'
set style fill solid noborder
set object circle at first 0,0 front size r1 arc [0:60] fillcolor rgb 'red'
set object circle at first 0,0 front size r1 arc [60:160] fillcolor rgb 'green'
set object circle at first 0,0 front size r1 arc [160:360] fillcolor rgb 'blue'
# Splat a white circle on top to conceal central area
set object circle at first 0,0 front size r2 fillcolor rgb 'white'
plot -10 notitle
And here is the result:
So, if you save the above script as annulus.cmd you would run it and create the file output.png using the command
gnuplot annulus.cmd
Obviously the guts of the script are the 3 lines that start set object circle each of which creates a separate annulus segment in a different colour with a different set of start and end angles.
Noodling around and changing some things gives this:
set xrange [-1:1]
set yrange [-1:1]
set angles degrees
set size ratio -1
# r1 = annulus outer radius, r2 = annulus inner radius
r1=1.0
r2=0.4
unset border; unset tics; unset key; unset raxis
set terminal png size 1000,1000
set output 'output.png'
set style fill solid noborder
set object circle at first 0,0 front size r1 arc [0:60] fillcolor rgb 'red'
set object circle at first 0,0 front size r1 arc [60:120] fillcolor rgb 'green'
set object circle at first 0,0 front size r1 arc [120:180] fillcolor rgb 'blue'
set object circle at first 0,0 front size r1 arc [180:240] fillcolor rgb 'yellow'
set object circle at first 0,0 front size r1 arc [240:300] fillcolor rgb 'black'
set object circle at first 0,0 front size r1 arc [300:360] fillcolor rgb 'magenta'
# Splat a white circle on top to conceal central area
set object circle at first 0,0 front size r2 fillcolor rgb 'white'
plot -10 notitle
As I am better at thinking in straight lines than circles, I thought I would have another go at this, a totally different way...
First, draw our annulus out in a straight line like this:
convert -size 45x40 xc:red xc:lime -size 270x40 xc:blue +append line.png
I sneakily made the lengths of the line segments add up to 360, so that there is one pixel per degree - for my simple brain to cope with :-) So, there are 45 px (degrees) of red, 45 px (degrees) of lime and 270 pixels (degrees) of blue, and they are all appended together with +append to make the line. Note that the first -size 45x40 setting persists until later changed, so it applies to both the red and lime line segments before I change it ready to apply to the blue.
Now we bend that line around a circle, like this:
convert line.png -virtual-pixel White -distort arc 360 result.png
You can also do it all in one go when you get used to the concept, like this:
convert -size 60x40 xc:red xc:lime xc:blue xc:cyan xc:magenta xc:black +append -virtual-pixel White -distort arc 360 result.png
You can add grey borders to your annulus segments like this:
convert -size 600x400 xc:red xc:lime xc:blue xc:cyan xc:magenta xc:black -bordercolor "rgb(180,180,180)" -border 20 +append -virtual-pixel White -distort arc 360 result.png
If you want everything on a transparent background, change all the white above to none.

Coloring an 8-bit grayscale image in MATLAB

I have an 8-bit grayscale image with different values (0,1,2,3,4,..., 255). What I want to do is color the grayscale image with colors like blue, red, etc. Until now, I have been doing this coloring but only in a greyscale. How can I do it with actual colors?
Here is the code I have written so far. This is where I am searching for all values that are white in an image and replacing them with a darkish gray:
for k = 1:length(tifFiles)
baseFileName = tifFiles(k).name;
fullFileName = fullfile(myFolder, baseFileName);
fprintf(1, 'Now reading %s\n', fullFileName);
imageArray = imread(fullFileName);
%// Logic to replace white grayscale values with darkish gray here
ind_plain = find(imageArray == 255);
imageArray(ind_plain) = 50;
imwrite(imageArray, fullFileName);
end
What you are asking is to perform a pseudo colouring of an image. Doing this in MATLAB is actually quite easy. You can use the grayscale intensities as an index into a colour map, and each intensity would generate a unique colour. First, what you need to do is create a colour map that is 256 elements long, then use ind2rgb to create your colour image given the grayscale intensities / indices of your image.
There are many different colour maps that are available to you in MATLAB. Here are the current available colour maps in MATLAB without the recently added Parula colour map that was introduced in R2014:
How the colour maps work is that lower indices / grayscale values have colours that move toward the left side of the spectrum and higher indices / grayscale values have colours that move toward the right side of the spectrum.
If you want to create a colour map with 256 elements, you simply use any one of those colour maps as a function and specify 256 as the input parameter to generate a 256 element colour map for you. For example, if you wanted to use the HSV colour map, you would do this in MATLAB:
cmap = hsv(256);
Now, given your grayscale image in your MATLAB workspace is stored in imageArray, simply use ind2rgb this way:
colourArray = ind2rgb(double(imageArray)+1, cmap);
The first argument is the grayscale image you want to pseudocolour, and the second input is the colour map produced by any one of MATLAB's colour mapping functions. colourArray will contain your pseudo coloured image. Take note that we offset the grayscale image by 1 and also cast to double. The reason for this is because MATLAB is a 1-indexed programming language, so we have to start indexing into arrays / matrices starting at 1. Because your intensities range from [0,255], and we want to use this to index into the colour map, we must make this go from [1,256] to allow the indexing. In addition, you are most likely using uint8 images, and so adding 1 to a uint8 will simply saturate any values that are already at 255 to 255. We won't be able to go to 256. Therefore, you need to cast the image temporarily to double so that we can increase the precision of the image and then add 1 to allow the image to go to 256 if merited.
Here's an example using the cameraman.tif image that's part of the image processing toolbox. This is what it looks like:
So we can load in that image in MATLAB like so:
imageArray = imread('cameraman.tif');
Next, we can use the above image, generate a HSV colour map then pseudocolour the image:
cmap = hsv(256);
colourArray = ind2rgb(imageArray+1, cmap);
We get:
Take note that you don't have to use any of the colour maps that MATLAB provides. In fact, you can create your own colour map. All you have to do is create a 256 x 3 matrix where each column denotes the proportion of red (first column), green (second column) and blue (third column) values per intensity. Therefore, the first row gives you the colour that is mapped to intensity 0, the second row gives you the colour that is mapped to intensity 1 and so on. Also, you need to make sure that the intensities are floating-point and range from [0,1]. For example, these are the first 10 rows of the HSV colour map generated above:
>> cmap(1:10,:)
ans =
1.0000 0 0
1.0000 0.0234 0
1.0000 0.0469 0
1.0000 0.0703 0
1.0000 0.0938 0
1.0000 0.1172 0
1.0000 0.1406 0
1.0000 0.1641 0
1.0000 0.1875 0
1.0000 0.2109 0
You can then use this custom colour map into ind2rgb to pseudocolour your image.
Good luck and have fun!

Customizing colormap for Matlab

I am not sure how to approach this problem. I am trying to find good resources on how to customize a colormap (without using colormap editor) to set your colors and your boundaries for an image in Matlab. I have managed to do it using the colormap editor but I want to figure out how to do it manually.
I am trying to make a colormap that ranges between 0 and 127. The boundaries would be:
0 to 64 is black (0) to white (64)
65 to 127 is blue (65) to red (127)
Can someone give me some advice on how to manually make these changes to the colormap? A good resource would also be useful.
Thanks.
I suggest using linspace. It helps you creating a uniform distribution of numbers in some range.
blackToWhite = repmat(linspace(0,1,66),3,1)' ;
l1 = linspace(0,1,127-65+1);
blueToRed = [flipud(l1(:)) zeros(size(l1(:))) l1(:) ];
cmap = [blackToWhite; blueToRed];
The idea is to interpolate [1 0 0] up to [0 0 1]; Each of the color channels, red green and blue is interpolated on its own.
Red -> 1 ... 0
Green -> 0 ... 0
Blue -> 0 ... 1
So I generated l1 both for the red and the blue channel, but flipped it in one of them.

Resources