User:LindaB Helendale/BMPbase64
Create BMP image in-world
Introduction
Creating textures in-world is not possible in SL, but for some tasks it is useful to be able to produce images with scripts. Such as making sculpt maps, or visualizing some land attributes. The following script functions print out html code for a base64 encoded BMP image.
Usage
Copy and paste the script output from chat to a text file and save as .htm or .html file on the computer. You don't need to remove other chat lines that may have occurred while the script prints the image. After saving the file, open it with any web browser. The image can be saved in BMP format form the web browser to upload in SL or to use in any image manipulation applications.
The following script is a working demo. To use the functions in your script copy the segment from
<lsl>
//======= BMP image routines ===============================================
</lsl>
to
<lsl>
//=== end of BMP image routines ======================
</lsl>
into the script.
Main Script
<lsl> //======= BMP image routines =============================================== // // (c) LindaB Helendale, permission to use the routines freely is granted. // Please retain the copyright statement // // These routines print html code for showing a base64 encoded BMP image. // Copy and paste the chat between <html> and </html> to a file, and save, // and open with web browser. You can save it as BMP in the browser to upload in SL. // // Usage: // BMP_open_stream(width, heigth) - opens the BMP data stream and prints the header // The pixels are added with the following functions. The rows run from bottom to top // and columns from left to rigth. // BMP_add_pixel_color(colorVec) - adds pixel defined by color vector to the stream // BMP_add_pixel_RGB(R,G,B) - adds pixel defined by integers R G B to the stream // BMP_close_stream() - closes the stream
// Output line length can be about 1000 in SL,
// but take care that the application where you paste the data doesn't add line breaks
integer MAX_OUTPUT_LINE_LENGTH = 500;
string HTMLheader= "------ Copy to a file below this line ---------
<html><body><img id='image'><script language='JavaScript'> var data = 'data:image/bmp;base64,";
string HTMLtail= "var s_img = document.getElementById('image'); s_img.src = data; </script></body></html>
Copy to a file above this line ---------";
// to scale the image, you can add the following js commands in the tail before </script> // s_img.setAttribute('width', 512); s_img.setAttribute('height',512);
// header of the BMP image, byte per byte list BMPheader=[66,77, 54,48,0,0, 0,0,0,0, 54,0,0,0, 40,0,0,0, 32,0,0,0, 128,0,0,0, 1,0, 24,0, 0,0,0,0, 0,48,0,0, 194,30,0,0, 194,30,0,0, 0,0,0,0, 0,0,0,0];
list swap_bytes(integer J) {
return [ J & 0xff , (J>>8) & 0xff, (J>>16) & 0xff, (J>>24) & 0xff];
}
string BMP_encoded_header(integer width, integer heigth) {
// return encoded BMP header for the given size image, setting width, height, image size and file size string encodedHeader=""; list headerBytes=BMPheader; integer imSize = heigth * width * 3; integer fileSize = imSize + 54; headerBytes = BMPheader; headerBytes = llListReplaceList(headerBytes, swap_bytes(fileSize),2,5); headerBytes = llListReplaceList(headerBytes, swap_bytes(width),18,21); headerBytes = llListReplaceList(headerBytes, swap_bytes(heigth),22,25); headerBytes = llListReplaceList(headerBytes, swap_bytes(imSize),34,37); integer p; for(p=0;p<54;p+=3) { integer x = ((llList2Integer(headerBytes,p) & 0xff) << 24 ) | ((llList2Integer(headerBytes,p+1) & 0xff) << 16 ) | ((llList2Integer(headerBytes,p+2) & 0xff) << 8 ) ; string s=llGetSubString(llIntegerToBase64(x),0,3); encodedHeader += s; } return encodedHeader;
}
integer DATA_SIZE; // the required number of data items (3 byte/4 char) in the stream string LINEBUFF; // output buffer integer DATA_LINES; // the number of printed data lines, used for printing the js code for merging the data integer DATA_ITEMS; // the number of printed data items, used for error checking
BMP_open_stream(integer width, integer heigth) {
DATA_SIZE = width*heigth; DATA_LINES=0; DATA_ITEMS=0; LINEBUFF=""; string bmpheader=BMP_encoded_header(width,heigth); llOwnerSay("\n\n" + HTMLheader + bmpheader + "';\n/*");
}
BMP_close_stream() {
if (LINEBUFF != "") { llOwnerSay("\n*/\ndata" + (string)DATA_LINES + " = '" + LINEBUFF + "';\n/*"); LINEBUFF==""; DATA_LINES++; } // print the javascript code to append the data<line> variables to the data stream integer i; LINEBUFF = "\n*/\ndata+="; for(i=0;i<DATA_LINES;i++) { string s = "data" + (string)i + "+"; if (llStringLength(LINEBUFF) + llStringLength(s) + llStringLength(HTMLtail) > 1000) { llOwnerSay(llGetSubString(LINEBUFF,0,-2) + ";\n/*"); LINEBUFF = "\n*/\ndata+="; } LINEBUFF += s; if ((i+1) % 24 == 0) { LINEBUFF = llGetSubString(LINEBUFF,0,-2) + ";\ndata+="; } } llOwnerSay(llGetSubString(LINEBUFF,0,-2) + ";\n" + HTMLtail + "\n"); if (DATA_ITEMS != DATA_SIZE) { llOwnerSay("\nWarning: the size of the image is " + (string)DATA_SIZE + " pixels. \The number of pixels written is " + (string)DATA_ITEMS); }
}
BMP_add_pixel_color(vector rgb) {
// Add pixel specified by color vector to the stream rgb *= 255; integer x = ((integer)rgb.x<<8) | ((integer)rgb.y<<16) | ((integer)rgb.z<<24); string s=llGetSubString(llIntegerToBase64(x),0,3); BMP_add_3bytes_encoded(s);
}
BMP_add_pixel_RGB(integer R, integer G, integer B) {
// Add pixel specified by three integer RGB values to the stream integer x = (R<<8) | (G<<16) | (B<<24); string s=llGetSubString(llIntegerToBase64(x),0,3); BMP_add_3bytes_encoded(s);
}
BMP_add_3bytes_encoded(string s) {
// print the data in the stream, with new line if needed if (llStringLength(LINEBUFF) + llStringLength(s) > MAX_OUTPUT_LINE_LENGTH) { llOwnerSay("\n*/\ndata" + (string)DATA_LINES + "='" + LINEBUFF + "';\n/*"); LINEBUFF=s; DATA_LINES++; }else{ LINEBUFF += s; } DATA_ITEMS++;
} //=== end of BMP image routines ======================
// this is needed for the demo pic only vector hsv2rgb(vector hsv) {
float h=hsv.x; float s=hsv.y; float v=hsv.z; h = 6*h; integer k = llFloor(h-6*0.000001); if (k<0) { k=0;} float f = h-k; float t = 1-s; float n = 1-s*f; float p = 1-(s*(1-f)); float r = (k==0) + (k==1)*n + (k==2)*t + (k==3)*t + (k==4)*p + (k==5); float g = (k==0)*p + (k==1) + (k==2) + (k==3)*n + (k==4)*t + (k==5)*t; float b = (k==0)*t + (k==1)*t + (k==2)*p + (k==3) + (k==4) + (k==5)*n; float max=r; if (g>max) max=g; if (b>max) max=b; f = v/max; vector rgb=f*<r,g,b>; return(rgb);
}
default
{
// make test image: 64 x 12 image with // 2 pixel pink stripe on top // 8 pixel HSV chart in middle, with saturation growing upwards // 2 pixel lilac stripe below // in BMP the starting row is at bottom, columns run from left ro right touch_start(integer p) { if (llDetectedKey(0)!=llGetOwner()) return; integer R=12; integer C=64; BMP_open_stream(C,R); integer row; integer col; for(row=0;row<R;row++) { for(col=0;col<C;col++) { vector rgb; if (row<=1) { // lilac stripe rgb = <100,0,255>/255.0; }else if (row>=10) { // pink stripe rgb = <255,170,230>/255.0; }else{ // HSV chart vector hsv=<(float)col / (float)(C), (float)(row-2) / 7.01 , 1>; rgb=hsv2rgb(hsv); } BMP_add_pixel_color(rgb); } } BMP_close_stream(); }
} </lsl>