|
Post by meerkat on Dec 13, 2023 7:47:55 GMT -5
As a newbe to LB, I'm not sure how to handle this. I want to convert a image file to a gray scale output. Create lines in the output with the width of the line representing the darkness of the gray scale. Is this even possible? Anyone have any ideas? Here is how vCarve handles it: PhotoVCarveThanks for any help..
|
|
|
Post by tsh73 on Dec 13, 2023 8:35:07 GMT -5
|
|
|
Post by tenochtitlanuk on Dec 13, 2023 8:38:27 GMT -5
I've played quite a lot with this kind of thing. See www.diga.me.uk/rastWidth.html or www.diga.me.uk/svgGen.html among others on my site. Imge below is of me 60 years ago... LB4 handles only bmp images. You'll find on my site and elsewhere ways to load other types. There are image loading dlls out there- or you could like me use ImageMagick. Image processing is SLOW if you use a getpixel routine to find local brightness. Quicker is to use say ppm format, which amounts to an x/y array of pixel values, and read it by indexing. See www.diga.me.uk/RCbitmapPnp.html for example. Worth too looking at EggBot pages- there is a great rasterizing routine there.
|
|
|
Post by meerkat on Dec 13, 2023 8:54:35 GMT -5
thanks you vary much tenochtitlanuk.
I need to learn more about image processing with LB. I was using Javascript and it looked promising.
You hit the nail on the head. Looks like exactly what I want. I'll give it a try on my cnc and see how it turns out. I'll send some images after I play with it for a while.
I wish LB would add CGI for web and html. Then I'm move 100% to LB. But currently most of my stuff is client/server.
|
|
|
Post by meerkat on Dec 13, 2023 9:30:05 GMT -5
here is basically the same code in JavaScript. I'm doing 20 lines of the image on, and skiping 20 lines. That can be altered however if you want more or less detail on the cnc. I need to make it all black and white however and that is really simple. Change the bmp file at the end of the program to your bmp; img.src = 'dk.bmp'; // Replace with the actual name of your image file Since JS uses relative files, I put dk.bmp in the same directory as the javascript code.
To run it save the file as something like a.html. then click on the a.html to run it in the browser. Remember to put the bmp file in the same directory. Dan
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Display</title> <style> canvas { border: 1px solid #000; } </style> </head> <body>
<canvas id="displayCanvas" width="850" height="660"></canvas>
<script> const canvas = document.getElementById('displayCanvas'); const ctx = canvas.getContext('2d');
let img = new Image(); img.onload = function() { let startY = 20; let lineHeight = 20;
// Loop through the image, displaying 20 lines of image followed by 20 lines of blank space for (let y = 0; y < img.height; y += lineHeight * 2) { // Draw 20 lines of the image ctx.drawImage(img, 0, y, img.width, lineHeight, 20, startY, img.width, lineHeight);
// Draw 20 lines of blank space ctx.clearRect(20, startY + lineHeight, img.width, lineHeight);
// Adjust the startY position for the next set of lines startY += lineHeight * 2; } }; img.src = 'dk.bmp'; // Replace with the actual name of your image file
</script>
</body> </html>
|
|
|
Post by Rod on Dec 13, 2023 10:26:13 GMT -5
A question, I am confused about black and white and gray scale.
I was thinking that the gray scale was achieved by varying the depth of cut. So say a line is 1mm wide, no cut leaves it all black, full cut means the whole 1mm is cut leaving white. I assume we are cutting a black faced board.
So to get variation the line across the image varies the cut from 1mm to 0mm width?
Or how does the cnc machine vary color? Knowing that we can slice up an image and ways.
|
|
|
Post by meerkat on Dec 13, 2023 11:38:37 GMT -5
Yes.. CNC machines do not understand color. if you are doing carving, or so-called 2.5d then the depth of the cut is based on the gray scale. The darker the deeper. However when doing portrates, it does not work. For example if you have a black mustasch it will cut a deep hole below your nose. So to get around this they do the vPhotoCarving, and you don't set up your machine for carving, instead you set up toolpaths. Then you import a image to cnc and tell it to trace the image. If you have a 1/8 inch black line, it draws a box around it. Using a 90deg vBit and you tell it to go 1 inch in depth, it will try to gouge out the square and stop when the edges of a vBit touch the inside of the box it traced. It will go deeper if you use a 60 degree bit. All shapes in cnc must be closed. So for irregular shapes it cuts along the line and mackes sure the vbit goes deep enough to touch the insides of the shape, but only to the max depth you requested.
I'm in the process of changing the javascript to average the pieexl for the 20 lines and put out a black box for the height of the average gray color of the 20 bits. Put them all together it looks like a varying shape black line 1/8" high, or whatever you want. Then the cnc reads the image and outlines the black image.
It's hard to explain.. So if it don't make sense let me know, and I'll try to clarify anything.
|
|
|
Post by tenochtitlanuk on Dec 13, 2023 13:00:59 GMT -5
By plunging the router bit by an amount proportional to grey level we get a line width also proportional- but we are not aiming to make a hole. Only width matters. For monitor screen output I used circles with size governed by grey level. In CNC routing the '2.5D' gets the same visual effect.
|
|
|
Post by Rod on Dec 15, 2023 6:09:30 GMT -5
I remain interested in this subject. I was thinking of hacking out the Pan/Crop code from my spritemaker program and building a router review image rather than a sprite image. The router file would be step two. But first I need to see if my thinking is clear.
So lets say we wished to router a 100mm x 100mm plaque. Lets say the router step resolution is .02mm So if I split the image into 100x100 1mm squares each square could be routed from 0mm - 1mm depth in 50 steps. That would give 50 shades of gray.
Or I could just do 1mm wide lines with 500 steps across the plaque 0-1mm deep. That would probably enhance the image resolution.
If anyone wishes to correct my thinking please do. I hope to post a router viewer, not sure how long it will take! The viewer will take any image, allow you to Pan and Crop the portion of the image you are interested in and it will then import at the desired resolution and color scale it to the desired cutting depth/resolution.
Or, do we just need a grayscale image at the desired resolution both pixel width height and color depth?
|
|
|
Post by Rod on Dec 16, 2023 7:17:22 GMT -5
Playing with some old code. This will take an input image of any size and squash it into a 100x100x50 color image. It lets you see what the routed image might look like. The aspect ratio could be preserved with changes. 50 shades of gray, impressive image! If there is any interest I will keep going and add in the Pan Crop part. WindowWidth = 700 WindowHeight = 400 UpperLeftX = (DisplayWidth-WindowWidth)/2 UpperLeftY = (DisplayHeight-WindowHeight)/2 graphicbox #1.gb1,10,10,300,300 graphicbox #1.gb2,330,10,100,100 open "SET GET Pixel" for graphics_nf_nsb as #1 #1 "trapclose [quit]" global bmpw,bmph,padding,offset,bytes,Blue,Green,Red
[loadbmp] filedialog "Choose an image","*.bmp",file$ if file$<>"" then 'display the original bmp in gb1 loadbmp "original",file$ #1.gb1 "down ; drawbmp original 0 0" #1.gb2 "down"
'now open the same bmp file for binary read/write access open file$ for binary as #bmp
'analyse the file header bytes seek #bmp,10 'picture data offset, where the color data starts 4 bytes of data offset=asc(input$(#bmp,1))+asc(input$(#bmp,1))*256+asc(input$(#bmp,1))*65536+asc(input$(#bmp,1))*16777216 seek #bmp,18 'width 4 bytes of data bmpw=asc(input$(#bmp,1))+asc(input$(#bmp,1))*256+asc(input$(#bmp,1))*65536+asc(input$(#bmp,1))*16777216 seek #bmp,22 'height 4 bytes of data bmph=asc(input$(#bmp,1))+asc(input$(#bmp,1))*256+asc(input$(#bmp,1))*65536+asc(input$(#bmp,1))*16777216 seek #bmp,28 'bits per pixel, ie color depth 2 bytes of data bits=asc(input$(#bmp,1))+asc(input$(#bmp,1))*256
'work out start of picture data and how to move through file pointer=offset
'work out how many bytes in the bits per pixel 24=3 bgr 32=4 abgr bytes=bits/8
'work out padding each raster line must be a 4byte multiple mult=bits/8*bmpw/4 padding = 4*(1-(mult-int(mult))) mod 4 close #bmp
'now we know the width and height of the input bmp work out our compression factors 'so that the image is compressed to say 100x100 router points 'padw and padh are the pixels we throw away at the edge of the image outw=int(bmpw/100) padw=bmpw-outw*100 outh=int(bmph/100) padh=bmph-outh*100
end if
[compressall] 'now we average the color of every block of pixels s=time$("ms") print "started" print "the input bmp is ";bmpw;" by ";bmph print "the output bmp is 100 by 100" print "the block width is ";outw print "the block height is ";outh open file$ for binary as #bmp 'run through all blocks for y=0 to 99 for x=0 to 99 colavg=0 avg=0 'find the pixel xy of the starting point of the block px=x*outw py=y*outh for yy=py to py+outh for xx=px to px+outw nul=getpixel(xx,yy) colavg=colavg+Red colavg=colavg+Green colavg=colavg+Blue avg=avg+1 next next 'lets average all of the rgb color values colavg=int(colavg/avg) 'now divide by three to get grayscale value 0-255 colavg=int(colavg/3) 'now break this into 50 levels of color c=255/50 ci=int(255/50) colavg=int(colavg/c)*ci #1.gb2 "color ";colavg;" ";colavg;" ";colavg #1.gb2 "set ";x;" ";y next next close #bmp wait
[quit] close #1 end
function setpixel(x,y,r,g,b) 'raster lines are stored bottom up so we invert y y=(bmph-1)-y pointer=offset+x*bytes+y*(bmpw*bytes+padding) 'set pixel color seek #bmp, pointer #bmp chr$(b);chr$(g);chr$(r); end function
function getpixel(x,y) 'raster lines are stored bottom up so we invert y y=(bmph-1)-y pointer=offset+x*bytes+y*(bmpw*bytes+padding) 'get pixel color stored as BGR Liberty needs RGB seek #bmp, pointer Blue=asc(input$(#bmp,1)) Green=asc(input$(#bmp,1)) Red=asc(input$(#bmp,1)) end function
|
|
|
Post by tsh73 on Dec 16, 2023 10:41:28 GMT -5
There was a time when 16 shades of gray was really cool (PALM, Sony CLIE, 320x320 3" square screen)
|
|
|
Post by meerkat on Dec 17, 2023 4:24:53 GMT -5
Finally got a little time to play with this. I'm trying a combination of display line widths and skip line widths. I'm trying to zero in on what works best. I'm finding it kinda depends on the photo. One will work best with a certain display Line and skip Lines, and another will not. not sure how to resolve that? It's written in Javascript. For me JS works great since most of my stuff is client/server, and I want the program to run on the client side. Here is the code and it's interesting to see what the results are with different display lines and skip lines. To run it place it in a *.html file, click it, and it should run in your favoriate browser.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>File to Grayscale</title> </head> <body>
<label for="displayLines">Lines to Display:</label> <input type="number" id="displayLines" value="5">
<label for="skipLines">Lines to Skip:</label> <input type="number" id="skipLines" value="2"><BR> <input type="file" id="fileInput" accept=".bmp, .jpg, .jpeg, .png"><BR> <button onclick="convertToGrayscale()">Convert to Grayscale</button><BR>
<canvas id="outputCanvas" width="400" height="400"></canvas>
<script> function convertToGrayscale() { fileInput = document.getElementById('fileInput'); canvas = document.getElementById('outputCanvas'); ctx = canvas.getContext('2d');
displayLines = parseInt(document.getElementById('displayLines').value, 10) || 20; skipLines = parseInt(document.getElementById('skipLines').value, 10) || 10;
file = fileInput.files[0];
if (file) { reader = new FileReader();
reader.onload = function (e) { img = new Image(); img.onload = function () { canvas.width = img.width; canvas.height = img.height;
let startY = 0;
// Loop through the image, displaying specified lines and skipping specified lines for (let y = 0; y < img.height; y += (displayLines + skipLines)) { if (y + displayLines <= img.height) { // Display specified lines of the image ctx.drawImage(img, 0, y, img.width, displayLines, 0, startY, img.width, displayLines);
// Convert displayed lines to grayscale imageData = ctx.getImageData(0, startY, img.width, displayLines); data = imageData.data;
for (let i = 0; i < data.length; i += 4) { average = (data[i] + data[i + 1] + data[i + 2]) / 3; // For simplicity, I'm using the average value as grayscale, you can modify this part as needed data[i] = average; data[i + 1] = average; data[i + 2] = average; }
ctx.putImageData(imageData, 0, startY); } else { // Draw remaining lines with a blank or white background ctx.fillStyle = 'rgb(255, 255, 255)'; ctx.fillRect(0, startY, img.width, skipLines); }
startY += displayLines + skipLines; } };
img.src = e.target.result; };
// Read the file content based on its type if (file.type.startsWith('image')) { reader.readAsDataURL(file); } else { alert('Invalid file type. Please select an image file.'); } } else { alert('Please select a file.'); } } </script>
</body> </html>
|
|
|
Post by Rod on Dec 17, 2023 12:39:17 GMT -5
HTML works well for me. But it seems to just pick out certain lines. In the code I posted I average all of the pixels in one block to merge/compress the image without loosing lots of data.
Well the compressed picture looks good. But how to turn it into a useable router file?
|
|
|
Post by meerkat on Dec 17, 2023 13:03:18 GMT -5
I'm using carbide create software as a cnc design tool. You can ask it to load a image. It then outlines all the dark areas. So if you have a black line that is 1/4 inch high and 4 inches wide, it will outline it. You will get a box that is 1/4 inch high and 4 inches long. It does this for all the lines that are in the photo. You tell the software to use all those outlines. It will then try to V-carve the inside of the boxes. The wider the box the deeper the V cut.
The carbide create software may explain it better than I can...
|
|
|
Post by tsh73 on Dec 21, 2023 16:03:56 GMT -5
I had the ideas but I hadn't got a time Finally I made some Here's 100x100 penny dephmap (googled penny dephmap, resized 100x100, saved as 32 bit) save it as penny.bmp And here is result of a program It does move in zigzag fasion like CNC cutter it draws with wariable-width white dot over black background it moves in single pixel but uses inerpolated data from 100x100 bitmap (probably you could use 600x600 bitmap without interpolation, picking colors from source bitmap along path. But then you will not use EVERY point of bitmap, anyway - I doubt there will be much visible difference) It might be using some parametrising (like zigzag size) but it's night in my path of the world. 'CNC like drawing 'tsh73 Dec 2023 'zig-zag path, drawing with variable white dots on black 'used 100x100 penny dephmap 'googled penny dephmap, resized 100x100, saved as 32 bit
'v5: add intepolating (copied from old fisheye program) ' not bothering to understand how it works 'v6: CNC zigzag path 'v7: change set x y to drawing interpolated point
fname$ = "penny.bmp" '32 bit, greyscale, 100x100 nomainwin
call GetBmpDimensions fname$, width, height print fname$;" is ";width;" x ";height
open fname$ for input as #bmp fLen=lof(#bmp) offSet=fLen-4*width*height seek #bmp, offSet bmp$=input$(#bmp, fLen-offSet) close #bmp
print "fLen", fLen print "offset", offSet print "len(bmp$)", len(bmp$)
sz=6 WindowHeight = 40+sz*height WindowWidth = 10+sz*width
open "test" for graphics_nsb_nf as #gr #gr "trapclose [quit]" #gr "home; posxy cx cy" #gr "down"
#gr "fill black" #gr "color white"
#gr "size ";sz
lineLen=4*width
dim counter(255, 2) for i = 1 to 255:counter(i, 1)=i: next
dim b(width+1,height+1) 'color actually 00 BB GG RR, but for grays, take BB
'fill array for y = 0 to height for x = 0 to width SCAN b(x,height-y)= asc(mid$(bmp$, 2+y*lineLen+x*4, 1)) 'print x, y , b(x,y) next next
sz2=sz*sqr(2) x=0 y=int(sz2) #gr "size 1" d=1 'move up/ till y <= 0 'move -> for sz2 'move down/ till x<=0 'move down for sz2 'that will be triangle
'upper left triangle while 1 while y > 0 '#gr "size 1" '#gr "set ";x;" ";y gosub [drawInterpolatedDot] x=x+d:y=y-d 'call pause 50 wend '#gr "color cyan " xx=x+sz2 while x < xx '#gr "set ";x;" ";y gosub [drawInterpolatedDot] x=x+d 'call pause 50 wend '#gr "color pink " while x > 0 '#gr "set ";x;" ";y gosub [drawInterpolatedDot] x=x-d:y=y+d 'call pause 50 wend '#gr "color yellow " yy=y+sz2 while y < yy '#gr "set ";x;" ";y gosub [drawInterpolatedDot] y=y+d 'call pause 50 wend if y >height*sz then exit while wend 'now same but lower right triangle y=height*sz 'or I got out x=0 while 1 '#gr "color pink" while x < width*sz '#gr "set ";x;" ";y gosub [drawInterpolatedDot] x=x+d:y=y-d 'call pause 50 wend '#gr "color cyan " yy=y+sz2 while y < yy '#gr "set ";x;" ";y gosub [drawInterpolatedDot] y=y+d 'call pause 50 wend '#gr "color pink " while y < height*sz '#gr "set ";x;" ";y gosub [drawInterpolatedDot] x=x-d:y=y+d 'call pause 50 wend '#gr "color yellow " xx=x+sz2 while x < xx '#gr "set ";x;" ";y gosub [drawInterpolatedDot] x=x+d 'call pause 50 wend if x >(width-2)*sz then exit while '-2 is a crutch against out of array wend
wait
[drawInterpolatedDot] x1=x/sz y1=y/sz 'get source color of that point 'interpolation between 4 points xa1 = int(x1) xb1 = xa1+1 ya1 = int(y1) yb1 = ya1+1
xb = x1-xa1 xa = 1-xb yb = y1-ya1 ya = 1-yb
w11=xa*ya w21=xb*ya w12=xa*yb w22=xb*yb
b= w11*b(xa1, ya1)+ _ w21*b(xb1, ya1)+ _ w12*b(xa1, yb1)+ _ w22*b(xb1, yb1) c1=int(b) szz=1.4*sz*(c1-50)/206 #gr "size ";szz if szz>0.5 then 'it looks I got something wrong for negatives #gr "set ";x;" ";y end if return
#gr "flush" wait
[quit] close #gr end
'----------------------------- sub GetBmpDimensions fileName$, byref width, byref height open fileName$ for input as #gbd temp$ = input$(#gbd, 24) close #gbd width = asc(mid$(temp$, 19, 1))+asc(mid$(temp$, 20, 1))*256 height = asc(mid$(temp$, 23, 1))+asc(mid$(temp$, 24, 1))*256 end sub
sub pause mil t0=time$("ms") while time$("ms")-t0<mil scan wend end sub
|
|