![]() |
Forum Index : Microcontroller and PC projects : Dittering Pictures to PicoMite
![]() ![]() |
|||||
Author | Message | ||||
Martin H.![]() Guru ![]() Joined: 04/06/2022 Location: GermanyPosts: 1293 |
I Found a Video where javidx9 (One lone Coder) explains, how to Program Dithering Floyd-Steinberg Dithering. Seems to be not much, so I'II give it a try next week. Dithering with Floyd-Steinberg in C++ 'no comment |
||||
Martin H.![]() Guru ![]() Joined: 04/06/2022 Location: GermanyPosts: 1293 |
Thank you for showing another option. Very nice Output Pictures. Good Job ![]() For using them on the Pico, they have to be transfered back to 24Bit BMP or to Jpg. Cheers Mart!n Edited 2022-12-24 18:23 by Martin H. 'no comment |
||||
homa![]() Guru ![]() Joined: 05/11/2021 Location: GermanyPosts: 481 |
Hello, I have found the following tool: http://christoph.laeubrich.de/files/uploads/19/grafikkonverterv1_4.zip Detail here: https://www.mikrocontroller.net/topic/92623 or also here https://github.com/laeubi/grafikkonverter-tool Unfortunately you have to enter the 16 colors once as a palette at the beginning, this can also not be saved :-( Maybe someone can do something with the sources ... Results see attached. London extracted from the input post. The bird and a test picture of me ... ![]() ![]() ![]() ![]() ![]() ![]() |
||||
homa![]() Guru ![]() Joined: 05/11/2021 Location: GermanyPosts: 481 |
![]() |
||||
Martin H.![]() Guru ![]() Joined: 04/06/2022 Location: GermanyPosts: 1293 |
Nice, ![]() Unfortunately I don't know enough about Java to find the part, where you could store or load the palette. The results are really great. ![]() btw: Happy New Year ![]() Edited 2023-01-01 01:30 by Martin H. 'no comment |
||||
homa![]() Guru ![]() Joined: 05/11/2021 Location: GermanyPosts: 481 |
If then true to style ;-) Happy New Year! ![]() |
||||
Martin H.![]() Guru ![]() Joined: 04/06/2022 Location: GermanyPosts: 1293 |
I Think I've found the(by now) best solution. Here is an Online Ditherer, where you can save (copy and Past) a Custom Color Palette and it dithers in many different ways: https://ditherit.com/ the Custom Palette for the Pico to past in looks like this: [{"hex":"#000000"}, {"hex":"#FF0000"}, {"hex":"#0000FF"}, {"hex":"#FF00FF"}, {"hex":"#004000"}, {"hex":"#FF4000"}, {"hex":"#0040FF"}, {"hex":"#FF40FF"}, {"hex":"#008000"}, {"hex":"#FF8000"}, {"hex":"#0080FF"}, {"hex":"#FF80FF"}, {"hex":"#00FF00"}, {"hex":"#FFFF00"}, {"hex":"#00FFFF"}, {"hex":"#FFFFFF"}] Here the london png with Atkinson dithering looks like this ![]() Cheers Mart!n Edited 2023-01-03 01:25 by Martin H. 'no comment |
||||
Volhout Guru ![]() Joined: 05/03/2018 Location: NetherlandsPosts: 5345 |
Nice tool, this will help create better graphics. Volhout PicomiteVGA PETSCII ROBOTS |
||||
Martin H.![]() Guru ![]() Joined: 04/06/2022 Location: GermanyPosts: 1293 |
Working on the illustration for Colossal Cave Adventures has resulted in significant changes to my MMB 4 Windows tool. The program now determines the color thresholds itself. First, the statistical mean for each color (RGB) is calculated for 8x8 pixel blocks, then the median of this list is determined from all the values. These values are used as the threshold values. These values can be changed later using buttons. ![]() becomes ![]() 'MMB4W Modes etc. Loads a source PNG and translatet it to 121 RGB BMP ' 'Enter Source Filename here FN$="testapple" '--------------------------------- Mode 1 dim w, h as integer open FN$ + ".png" for input as #1 s$ = INPUT$(24, #1) ' Erste 24 Bytes holen (bis Ende IHDR-Header) ' Breite aus Byte 17-20 (Index 16–19 in MMBasic, da 0-basiert) w = ASC(MID$(s$,17,1))*16777216 + ASC(MID$(s$,18,1))*65536 + ASC(MID$(s$,19,1))*256 + ASC(MID$(s$,20,1)) ' Höhe aus Byte 21–24 (Index 20–23) h = ASC(MID$(s$,21,1))*16777216 + ASC(MID$(s$,22,1))*65536 + ASC(MID$(s$,23,1))*256 + ASC(MID$(s$,24,1)) close #1 BL%=(h*w)/64 Dim integer listR(BL%) Dim integer listG(BL%) Dim integer listB(BL%) cls load png FN$+".png" Prescan Scan ui save image FN$+".bmp",0,0,w,h end sub Scan for y=0 to h for x=0 to w cl%=0 n=Pixel(x,y)AND &HFFFFFF '-->get Red r=n AND &HFF 'Check red thresholds if value is in then dither if r>LR and r<UR then if (y mod 2)then R=255*not (x mod 2) else R=255*(x mod 2) end if end if inc cl%,255*(r>UR):n=int(n/256) '-->get Green g=n AND &HFF 'Check green thresholds, no dithering select case g case >UG inc cl%,&HFF00 case >MG inc cl%,&H8000 case >LG inc cl%,&H4000 end select '-->get Blue n=int(n/256) b=n AND &HFF 'Check Blue thresholds, if value is in then dither if b>LB and r<UB then if (y mod 2)then B=255*(x mod 2) else B=255*not (x mod 2) end if end if inc cl%,&Hff0000*(b>UB) pixel x,y,cl% next next End sub sub ui Font 2 px=w+6 do Print@(px,0);" threshold values" Print@(px,20);" RED GREEN Blue" Print@(px,40);" Q ";make$(LR);" W E ";make$(LG); PRINT " R O ";make$(LB);" P" Print@(px,60);" A ";make$(UR);" S D ";make$(MG); PRINT " F K ";make$(UB);" L" Print@(px,80);" C ";make$(UG);" V" Print@(px,100); Print@(PX,120);" SPACE = Repaint" Print@(PX,140);" Enter = Save&Exit" Print@(PX,160);" ESC = Exit without Save" do :k$=inkey$:Loop while k$="" k$=Ucase$(k$) select case k$ case "Q" if LR>0 then inc LR, -1 case "W" if LR<UR then inc LR case "E" if LG>0 then inc LG, -1 case "R" if LG<MG then inc LG case "O" if LB>0 then inc LB, -1 case "P" if LB<UB then inc LB case "A" if UR>LR then inc UR, -1 case "S" if UR<255 then inc UR case "K" if UB>LB then inc UB, -1 case "L" if UB<255 then inc UB case "C" if UG>MG then inc UG, -1 case "V" if UG<255 then inc UG case "D" if MG>LG then inc MG, -1 case "F" if MG<UG then inc MG case " " load png FN$+".png" Scan case chr$(13) ,chr$(10) Exit SUB case chr$(27) End end select loop end sub Function make$(Value) make$=STR$(Value) do while (len(make$)<3) make$="0"+make$ loop end Function sub Prescan FOR y = 0 TO h - 8 STEP 8 FOR x = 0 TO w - 8 STEP 8 sumR = 0 : sumG = 0 : sumB = 0 FOR dy = 0 TO 7 FOR dx = 0 TO 7 n = Pixel(x + dx, y + dy) AND &HFFFFFF r = n AND &HFF n = INT(n / 256) g = n AND &HFF n = INT(n / 256) b = n AND &HFF sumR = sumR + r sumG = sumG + g sumB = sumB + b NEXT dx NEXT dy ' Mittelwert pro Block blockR = INT(sumR / 64) blockG = INT(sumG / 64) blockB = INT(sumB / 64) ' In Medianliste schreiben listR(blockIndex) = blockR listG(blockIndex) = blockG listB(blockIndex) = blockB blockIndex = blockIndex + 1 NEXT x NEXT y pass = 0 do tausch = 0 For ss = 0 To BL% - 2 - pass If listR(ss) > listR(ss + 1) Then tt = listR(ss):listR(ss) = listR(ss + 1):listR(ss + 1) = tt:tausch = 1 End If Next Inc pass = pass Loop Until tausch = 0 pass = 0 do tausch = 0 For ss = 0 To BL% - 2 - pass If ListG(ss) > ListG(ss + 1) Then tt = ListG(ss):ListG(ss) = ListG(ss + 1):ListG(ss + 1) = tt:tausch = 1 End If Next Inc pass = pass Loop Until tausch = 0 pass = 0 do tausch = 0 For ss = 0 To BL% - 2 - pass If ListB(ss) > ListB(ss + 1) Then tt = ListB(ss):ListB(ss) = ListB(ss + 1):ListB(ss + 1) = tt:tausch = 1 End If Next Inc pass = pass Loop Until tausch = 0 LR=listR(BL%/3) 'Lower threshold Red UR=listR(2*(BL%/3)) 'Upper threshold LG=listG(BL%/4)'Lower threshold Green MG=ListG(2*(BL%/4)) 'Mid threshold UG=ListG(3*(BL%/4)) 'Upper threshold LB=listB(BL%/3) 'Lower threshold Blue UB=listB(2*(BL%/3)) 'Upper threshold end sub Maybe I'll give Atkinson a try after all. ![]() Cheers Martin Edited 2025-10-14 07:52 by Martin H. 'no comment |
||||
dddns Guru ![]() Joined: 20/09/2024 Location: GermanyPosts: 608 |
Fantastic thread, thank you all! I'm new to grafics and didn't have an idea how to get into that. Special thanks @scruss: Under Linux all there is to do: apt install libjpeg-progs djpeg bird_orig.jpg | pnmremap -floyd -mapfile=<(echo P3 16 1 3 0 0 0 0 2 0 0 2 3 3 3 3 0 3 3 3 0 3 3 0 0 3 1 0 3 2 3 3 2 0 3 1 3 0 3 0 3 3 0 0 1 3 0 0 3 0 1 0) | ppmtobmp > bird_orig_FS.bmp to have that wonderful result and that is exactly how it look on my unmodified VGA: load image "b:bird_orig_fs.bmp" : save image "pico_org.bmp" ![]() Thanks a lot!! Edit: Weils soo schön ist: ![]() Mode 3 @800x600 Edited 2025-10-14 08:47 by dddns |
||||
Martin H.![]() Guru ![]() Joined: 04/06/2022 Location: GermanyPosts: 1293 |
sub Prescan Accelerated many times (from 3-4S to 200ms) over and greatly shortened (Sometimes it doesn't hurt to look in the manual) please replace with: sub Prescan 'Determine the average values of each color in the image 'to auto calculate the thresholds '-------------------------------------------------- 'pass1: Sample and store the color-values of 8x8 blocks. local integer r,g,b,n,dx,dy,ss FOR y = 0 TO h - 8 STEP 8 FOR x = 0 TO w - 8 STEP 8 sumR = 0 : sumG = 0 : sumB = 0 FOR dy = y TO y+7 FOR dx = x TO x+7 n = Pixel(dx, dy) AND &HFFFFFF r=n AND &HFF:g=n AND &HFF00:b=n AND &HFF0000 inc sumR,r:inc sumG,g:inc sumB,b NEXT NEXT ' Values *64 (8x8)pro Block listR(blockIndex)=sumR:listG(blockIndex)=sumG:listB(blockIndex)=sumB inc blockIndex NEXT NEXT '2.Pass sort the sampled values for each color to find the median SORT ListR(), , , ,BL%:SORT ListG(), , , ,BL%:SORT ListB(), , , ,BL% LR=listR(BL%/3)>>6 'Lower threshold Red UR=listR(2*(BL%/3))>>6 'Upper threshold LG=listG(BL%/4)>>14 'Lower threshold Green MG=ListG(2*(BL%/4))>>14 'Mid threshold UG=ListG(3*(BL%/4))>>14 'Upper threshold LB=listB(BL%/3)>>22 'Lower threshold Blue UB=listB(2*(BL%/3))>>22 'Upper threshold end sub If you can't sleep, you might as well use the time wisely. 'no comment |
||||
matherp Guru ![]() Joined: 11/12/2012 Location: United KingdomPosts: 10481 |
Please don't take this the wrong way.... Here is an image on PICOMITE VGA RP2350 Mode 3 so 640x480 RGB121 standard palette using the new command timer=0:load dithered "b:/tiger640":?timer As you can see it took 1.3 seconds to load and draw. ![]() Here is the genesis of the command using CLAUDE Total time to create the fully working new capability which will be in the next release - less than 1 hour. The world really has changed ![]() Here is the code // BMP structures typedef struct __attribute__((packed)) { uint16_t bfType; uint32_t bfSize; uint16_t bfReserved1; uint16_t bfReserved2; uint32_t bfOffBits; } BITMAPFILEHEADER; typedef struct __attribute__((packed)) { uint32_t biSize; int32_t biWidth; int32_t biHeight; uint16_t biPlanes; uint16_t biBitCount; uint32_t biCompression; uint32_t biSizeImage; int32_t biXPelsPerMeter; int32_t biYPelsPerMeter; uint32_t biClrUsed; uint32_t biClrImportant; } BITMAPINFOHEADER; // Convert RGB121 to RGB888 void rgb121_to_rgb888_components(uint8_t color, uint8_t *r, uint8_t *g, uint8_t *b) { uint8_t r1 = (color >> 3) & 1; uint8_t g2 = (color >> 1) & 3; uint8_t b1 = color & 1; *r = r1 * 255; *g = g2 * 85; // 255/3 *b = b1 * 255; } // Convert RGB332 to RGB888 void rgb332_to_rgb888_components(uint8_t color, uint8_t *r, uint8_t *g, uint8_t *b) { uint8_t r3 = (color >> 5) & 7; uint8_t g3 = (color >> 2) & 7; uint8_t b2 = color & 3; *r = (r3 * 255) / 7; *g = (g3 * 255) / 7; *b = (b2 * 255) / 3; } // Convert RGB888 to RGB121 with dithering uint8_t rgb888_to_rgb121_dither(int16_t r, int16_t g, int16_t b) { // Clamp values r = (r < 0) ? 0 : (r > 255) ? 255 : r; g = (g < 0) ? 0 : (g > 255) ? 255 : g; b = (b < 0) ? 0 : (b > 255) ? 255 : b; // Convert to RGB121 (1 bit R, 2 bits G, 1 bit B) uint8_t r1 = (r >= 128) ? 1 : 0; uint8_t g2 = (g * 3 + 127) / 255; uint8_t b1 = (b >= 128) ? 1 : 0; return (r1 << 3) | (g2 << 1) | b1; } // Convert RGB888 to RGB332 with dithering uint8_t rgb888_to_rgb332_dither(int16_t r, int16_t g, int16_t b) { // Clamp values r = (r < 0) ? 0 : (r > 255) ? 255 : r; g = (g < 0) ? 0 : (g > 255) ? 255 : g; b = (b < 0) ? 0 : (b > 255) ? 255 : b; // Convert to RGB332 (3 bits R, 3 bits G, 2 bits B) uint8_t r3 = (r * 7 + 127) / 255; uint8_t g3 = (g * 7 + 127) / 255; uint8_t b2 = (b * 3 + 127) / 255; return (r3 << 5) | (g3 << 2) | b2; } int ReadAndDisplayBMP(int fnbr, int display_mode, int img_x_offset, int img_y_offset, int x_display, int y_display) { BITMAPFILEHEADER fileHeader; BITMAPINFOHEADER infoHeader; unsigned int bytes_read; int result; // Read file header result = IMG_FREAD(fnbr, &fileHeader, sizeof(BITMAPFILEHEADER), &bytes_read); if (result != 0 || bytes_read != sizeof(BITMAPFILEHEADER)) { error("BMP: Failed to read file header"); return -1; } // Verify BMP signature if (fileHeader.bfType != 0x4D42) { // "BM" error("BMP: Invalid file signature (not a BMP file)"); return -2; } // Read info header result = IMG_FREAD(fnbr, &infoHeader, sizeof(BITMAPINFOHEADER), &bytes_read); if (result != 0 || bytes_read != sizeof(BITMAPINFOHEADER)) { error("BMP: Failed to read info header"); return -3; } // Verify format - must be 24-bit RGB if (infoHeader.biBitCount != 24) { error("BMP: Must be 24-bit RGB888 format"); return -4; } // Verify no compression if (infoHeader.biCompression != 0) { error("BMP: Compressed format not supported"); return -5; } // Verify biPlanes is 1 (standard requirement) if (infoHeader.biPlanes != 1) { error("BMP: Invalid planes value (must be 1)"); return -6; } int width = infoHeader.biWidth; int height = infoHeader.biHeight; int is_bottom_up = (height > 0); if (height < 0) height = -height; // Check size limits if (width > 1920 || height > 1080 || width <= 0 || height <= 0) { error("BMP: Image dimensions invalid or exceed 1920x1080"); return -7; } // Calculate row size (rows are padded to 4-byte boundary) int row_size = ((width * 3 + 3) / 4) * 4; // Allocate buffers for Floyd-Steinberg dithering // We need to store error values for current and next line int16_t *error_buffer_r = (int16_t *)GetMemory(width * 2 * sizeof(int16_t)); int16_t *error_buffer_g = (int16_t *)GetMemory(width * 2 * sizeof(int16_t)); int16_t *error_buffer_b = (int16_t *)GetMemory(width * 2 * sizeof(int16_t)); if (!error_buffer_r || !error_buffer_g || !error_buffer_b) { if (error_buffer_r) FreeMemory((void *)error_buffer_r); if (error_buffer_g) FreeMemory((void *)error_buffer_g); if (error_buffer_b) FreeMemory((void *)error_buffer_b); error("BMP: Failed to allocate error buffers"); return -8; } // Initialize error buffers to zero for (int i = 0; i < width * 2; i++) { error_buffer_r[i] = 0; error_buffer_g[i] = 0; error_buffer_b[i] = 0; } int16_t *curr_error_r = error_buffer_r; int16_t *next_error_r = error_buffer_r + width; int16_t *curr_error_g = error_buffer_g; int16_t *next_error_g = error_buffer_g + width; int16_t *curr_error_b = error_buffer_b; int16_t *next_error_b = error_buffer_b + width; // Buffer for reading pixel data (5 lines max) int lines_per_buffer = 5; uint8_t *line_buffer = (uint8_t *)GetMemory(row_size * lines_per_buffer); if (!line_buffer) { FreeMemory((void *)error_buffer_r); FreeMemory((void *)error_buffer_g); FreeMemory((void *)error_buffer_b); error("BMP: Failed to allocate line buffer"); return -9; } // Buffer for output BGR triplets (maximum width for visible portion) int max_visible_width = (HRes < width) ? HRes : width; uint8_t *output_buffer = (uint8_t *)GetMemory(max_visible_width * 3); if (!output_buffer) { FreeMemory((void *)line_buffer); FreeMemory((void *)error_buffer_r); FreeMemory((void *)error_buffer_g); FreeMemory((void *)error_buffer_b); error("BMP: Failed to allocate output buffer"); return -10; } // Process image line by line for (int y = 0; y < height; y += lines_per_buffer) { int lines_to_read = (y + lines_per_buffer > height) ? (height - y) : lines_per_buffer; int bytes_to_read = row_size * lines_to_read; // Read lines result = IMG_FREAD(fnbr, line_buffer, bytes_to_read, &bytes_read); if (result != 0 || bytes_read != bytes_to_read) { FreeMemory((void *)output_buffer); FreeMemory((void *)line_buffer); FreeMemory((void *)error_buffer_r); FreeMemory((void *)error_buffer_g); FreeMemory((void *)error_buffer_b); error("BMP: Failed to read image data"); return -11; } // Process each line in the buffer for (int line = 0; line < lines_to_read; line++) { int current_y = y + line; int img_y = is_bottom_up ? (height - 1 - current_y) : current_y; int screen_y = img_y - img_y_offset + y_display; // Skip if line is outside display bounds if (screen_y < 0 || screen_y >= VRes) { continue; } // Clear next line error buffer for (int i = 0; i < width; i++) { next_error_r[i] = 0; next_error_g[i] = 0; next_error_b[i] = 0; } // Track visible pixels int visible_pixels = 0; int first_screen_x = -1; // Process each pixel in the line for (int x = 0; x < width; x++) { int pixel_offset = line * row_size + x * 3; uint8_t b = line_buffer[pixel_offset]; uint8_t g = line_buffer[pixel_offset + 1]; uint8_t r = line_buffer[pixel_offset + 2]; // Apply accumulated error from previous pixels int16_t old_r = r + curr_error_r[x]; int16_t old_g = g + curr_error_g[x]; int16_t old_b = b + curr_error_b[x]; // Convert to display format uint8_t display_color; uint8_t new_r, new_g, new_b; if (display_mode == DISPLAY_RGB121) { display_color = rgb888_to_rgb121_dither(old_r, old_g, old_b); rgb121_to_rgb888_components(display_color, &new_r, &new_g, &new_b); } else { // DISPLAY_RGB332 display_color = rgb888_to_rgb332_dither(old_r, old_g, old_b); rgb332_to_rgb888_components(display_color, &new_r, &new_g, &new_b); } // Clamp old values for error calculation old_r = (old_r < 0) ? 0 : (old_r > 255) ? 255 : old_r; old_g = (old_g < 0) ? 0 : (old_g > 255) ? 255 : old_g; old_b = (old_b < 0) ? 0 : (old_b > 255) ? 255 : old_b; // Calculate error int16_t err_r = old_r - new_r; int16_t err_g = old_g - new_g; int16_t err_b = old_b - new_b; // Distribute error using Floyd-Steinberg dithering if (x + 1 < width) { curr_error_r[x + 1] += (err_r * 7) / 16; curr_error_g[x + 1] += (err_g * 7) / 16; curr_error_b[x + 1] += (err_b * 7) / 16; } if (x > 0) { next_error_r[x - 1] += (err_r * 3) / 16; next_error_g[x - 1] += (err_g * 3) / 16; next_error_b[x - 1] += (err_b * 3) / 16; } next_error_r[x] += (err_r * 5) / 16; next_error_g[x] += (err_g * 5) / 16; next_error_b[x] += (err_b * 5) / 16; if (x + 1 < width) { next_error_r[x + 1] += (err_r * 1) / 16; next_error_g[x + 1] += (err_g * 1) / 16; next_error_b[x + 1] += (err_b * 1) / 16; } // Calculate screen position int screen_x = x - img_x_offset + x_display; // Store pixel if it's visible on screen if (screen_x >= 0 && screen_x < HRes) { if (first_screen_x == -1) { first_screen_x = screen_x; } // Store as BGR triplets output_buffer[visible_pixels * 3] = new_b; output_buffer[visible_pixels * 3 + 1] = new_g; output_buffer[visible_pixels * 3 + 2] = new_r; visible_pixels++; } } // Draw the line if any pixels are visible if (visible_pixels > 0) { int last_screen_x = first_screen_x + visible_pixels - 1; DrawBuffer(first_screen_x, screen_y, last_screen_x, screen_y, output_buffer); } // Swap error buffers int16_t *temp; temp = curr_error_r; curr_error_r = next_error_r; next_error_r = temp; temp = curr_error_g; curr_error_g = next_error_g; next_error_g = temp; temp = curr_error_b; curr_error_b = next_error_b; next_error_b = temp; } } // Cleanup FreeMemory((void *)output_buffer); FreeMemory((void *)line_buffer); FreeMemory((void *)error_buffer_r); FreeMemory((void *)error_buffer_g); FreeMemory((void *)error_buffer_b); return 0; } Edited 2025-10-14 21:26 by matherp |
||||
Martin H.![]() Guru ![]() Joined: 04/06/2022 Location: GermanyPosts: 1293 |
Atkinson Dithering During the mid-1980’s, dithering became increasingly popular as computer hardware advanced to support more powerful video drivers and displays. One of the best dithering algorithms from this era was developed by Bill Atkinson, a Apple employee who worked on everything from MacPaint (which he wrote from scratch for the original Macintosh) to HyperCard and QuickDraw. Atkinson’s formula is a bit different from others in this list, because it only propagates a fraction of the error instead of the full amount. This technique is sometimes offered by modern graphics applications as a “reduced color bleed” option. By only propagating part of the error, speckling is reduced, but contiguous dark or bright sections of an image may become washed out. X 1 1 1 1 1 1 https://tannerhelland.com/2012/12/28/dithering-eleven-algorithms-source-code.html ![]() FN$="TestApple" '--------------------------------- ' Atkinson Dithering v 0.9 'Converts PNG Source Files ' to RGB121 BMP ' by Martin H. 2025 '--------------------------------- Mode 1 'Header vom Quellbild "PNG" einlesen dim integer w, h open FN$ + ".png" for input as #1 s$ = INPUT$(24, #1) ' Erste 24 Bytes holen (bis Ende IHDR-Header) ' Breite aus Byte 17-20 (Index 16–19 in MMBasic, da 0-basiert) w = ASC(MID$(s$,17,1))*16777216 + ASC(MID$(s$,18,1))*65536 + ASC(MID$(s$,19,1))*256 + ASC(MID$(s$,20,1)) ' Höhe aus Byte 21–24 (Index 20–23) h = ASC(MID$(s$,21,1))*16777216 + ASC(MID$(s$,22,1))*65536 + ASC(MID$(s$,23,1))*256 + ASC(MID$(s$,24,1)) close #1 BL%=(h*w)/64 Dim integer listR(BL%),listG(BL%),listB(BL%) cls 'Quellbild laden load png FN$+".png" 'Atkinson Dithering, see ' https://tannerhelland.com/2012/12/28/dithering-eleven-algorithms-source-code.html Dim integer RLine(W+3,3),BLine(W+3,3),GLine(W+3,3) 'Scan beginn for Ypos=0 to H-1 for Xpos=1 to W ' n=Pixel(Xpos,Ypos)AND &HFFFFFF R = (N AND 255):RV=R+RLine(Xpos,1):'Pixel Xpos,Ypos,R B = ((N >> 16) AND 255) G= ((N >> 8) AND 255) 'Green GV = G + GLine(Xpos, 1) if GV > 255 then GV = 255 if GV < 0 then GV = 0 Index = INT(GV / 64 + 0.5) 'Calculate pallet value (G_Pallete). (0, 64, 128, 192, 255) G_Palette = Index * 64 if G_Palette > 255 then G_Palette = 255 GE = GV - G_Palette 'Red if RV>128 then RE=RV-255:CR=255 Else RE=RV:CR=0 'Pixel Xpos,Ypos+h,0:CR=0 end if 'Blue BV=B+BLine(Xpos,1) if BV>128 then BE=BV-255:CB=255 Else BE=BV:CB=0 end if Pixel Xpos,Ypos+h,RGB(CR,G_Palette,CB) 'Add to error list GE=GE/8 RE=RE/8:BE=BE/8:GE=GE/8 inc GLine(Xpos+1,1),GE:inc GLine(Xpos+2,1),GE inc GLine(Xpos-1,2),GE:inc GLine(Xpos+1,2),GE:inc GLine(Xposs,3),GE inc RLine(Xpos+1,1),RE:inc RLine(Xpos+2,1),RE: if Xpos>0 then inc RLine(Xpos-1,2),RE:inc BLine(Xpos-1,2),BE:inc GLine(Xpos-1,2),GE inc RLine(Xposs,2),RE:inc RLine(Xpos+1,2),RE:inc RLine(Xposs,3),RE inc BLine(Xpos+1,1),BE:inc BLine(Xpos+2,1),BE: inc BLine(Xposs,2),BE:inc BLine(Xpos+1,2),BE:inc BLine(Xposs,3),BE Next 'Next line, scrolling through the 3 lines of the error list for f=0 to W+3:RLine(F,1)=RLine(F,2):RLine(F,2)=RLine(F,3):RLine(F,3)=0: BLine(F,1)=BLine(F,2):BLine(F,2)=BLine(F,3):BLine(F,3)=0:Next Next save image FN$+".bmp",0,h,w,h ![]() 'no comment |
||||
![]() ![]() |
![]() |
![]() |
The Back Shed's forum code is written, and hosted, in Australia. | © JAQ Software 2025 |