Home
JAQForum Ver 24.01
Log In or Join  
Active Topics
Local Time 21:10 15 Nov 2025 Privacy Policy
Jump to

Notice. New forum software under development. It's going to miss a few functions and look a bit ugly for a while, but I'm working on it full time now as the old forum was too unstable. Couple days, all good. If you notice any issues, please contact me.

Forum Index : Microcontroller and PC projects : C development in the modern world (routine for Volhout)

Author Message
matherp
Guru

Joined: 11/12/2012
Location: United Kingdom
Posts: 10583
Posted: 07:34pm 13 Nov 2025
Copy link to clipboard 
Print this post

In the Picomite release thread Volhout asked
  Quote  What about the option to format a flash slot as a video plane (i.e. RGB121 320x240, or RGB222 320x240). And allow the BLIT command to copy data from it as it if where video data (tiles/sprites). This would be read only planes, that can only be written to with FLASH LOAD IMAGE "xxx.bmp".

You can achieve similar as in Petscii where we store non-compressed, or compressed video data in a flash slot. But that method is quite elaborate. It was needed for Petscii since there where so many sprites and tiles.

The key to this is a fast blit routine from one area of memory to another. So enter Claude stage left. I won't show the intermediate code steps to save space.

Me:
  Quote  I have 2 RGB222 framebuffers. In both cases they are HRes pixels wide and VRes pixels high. Pixels are packed 5 to a 32-bit word with 2 bits in each word unused. Write a C function that will take an area defined by x,y,w and h for one framebuffer and write it to an area also w by h but at x1,y1 in the other.
void blit222(int xsource, int ysource, int width, int height, int xdesination, int ydestination);


Claude:
  Quote  Here's a C function to blit between two RGB222 framebuffers with 5-pixels-per-word packing:
code here
This implementation:

Iterates pixel-by-pixel through the source region
Calculates word and pixel indices using division and modulo by 5
Extracts each 6-bit pixel from the source framebuffer
Writes the pixel to the corresponding position in the destination framebuffer
Includes bounds checking to handle clipping

For better performance with aligned blocks, you could optimize for cases where both source and destination are aligned to word boundaries, allowing you to copy entire words at once. Would you like me to add that optimization?


Me:
  Quote  optimise the routine where xsource and xdestination are both in the same position in a word (xsource % 5) == (xdestination %). You can transfer pixels up to the first word boundary individually and pixels after the last word boundary also individually. In between you can use memcpy


Claude:
  Quote  Here's an optimized version that uses memcpy for word-aligned sections:
code here
This optimization:

Checks alignment - only uses fast path when (xsource % 5) == (xdestination % 5)
Copies leading pixels individually until reaching a word boundary
Uses memcpy for all complete words in the middle
Copies trailing pixels individually for the remainder
Falls back to pixel-by-pixel for misaligned cases or when bounds checking is needed

The memcpy will be significantly faster for large aligned blits since it can transfer multiple pixels per memory operation.


Me:
  Quote  Now write a separate but similar routine where the transfer is within the same framebuffer.  If the two areas don't overlap you can call blit222. If they do overlap you will need to do a more complex transfer. The maximum buffer needed shouldn't be more than HRes\5 32-bit words


Claude:
  Quote  Here's a function for blitting within the same framebuffer, handling overlaps:
code here
This implementation:

Detects overlap using rectangle intersection test
Calls blit222 if no overlap (fast path)
Uses a line buffer of HRes/5 + 1 words for overlapping cases
Chooses direction - copies bottom-to-top if destination is below source
Copies row-by-row through the buffer to avoid corruption

The line buffer approach is efficient and only needs HRes/5 words maximum, which should be quite reasonable for typical screen resolutions (e.g., 640 pixels = 128 words = 512 bytes).


Me:
  Quote  model the two routines to confirm no errors


Claude:
  Quote  I'll create a test program to verify both routines work correctly:
code here
This test program:

Tests blit222 with:

Aligned blits (using memcpy optimization)
Misaligned blits (pixel-by-pixel)
Partial alignment with leading/trailing pixels


Tests blit222_same with:

Non-overlapping regions (should use fast path)
Overlapping with right shift
Overlapping with downward shift (bottom-to-top copy)
Overlapping with upward shift (top-to-bottom copy)


Verifies that all pixels are correctly copied by comparing expected vs actual values
Reports any errors found

The tests should all pass, confirming both routines work correctly!


I then take the code with test harness and paste into https://www.programiz.com/c-programming/online-compiler/ which runs the code passing all tests. Time to develop - 10 minutes

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

// Global framebuffer parameters
#define HRES 640
#define VRES 480
#define FB_WORDS ((HRES * VRES + 4) / 5)

uint32_t *sourceFB;
uint32_t *destFB;
uint32_t *frameBuf;
int HRes = HRES;
int VRes = VRES;

// Helper function to set a pixel in RGB222 format
void setPixel222(uint32_t *fb, int x, int y, uint8_t color) {
   if (x < 0 || x >= HRes || y < 0 || y >= VRes)
       return;
   
   int offset = y * HRes + x;
   int word_idx = offset / 5;
   int pixel_idx = offset % 5;
   int shift = pixel_idx * 6;
   
   uint32_t mask = ~(0x3F << shift);
   fb[word_idx] = (fb[word_idx] & mask) | ((color & 0x3F) << shift);
}

// Helper function to get a pixel in RGB222 format
uint8_t getPixel222(uint32_t *fb, int x, int y) {
   if (x < 0 || x >= HRes || y < 0 || y >= VRes)
       return 0;
   
   int offset = y * HRes + x;
   int word_idx = offset / 5;
   int pixel_idx = offset % 5;
   int shift = pixel_idx * 6;
   
   return (fb[word_idx] >> shift) & 0x3F;
}

void blit222(int xsource, int ysource, int width, int height, int xdestination, int ydestination) {
   // Check if source and destination have the same alignment within a word
   if ((xsource % 5) == (xdestination % 5)) {
       // Optimized path - can use memcpy for aligned sections
       for (int y = 0; y < height; y++) {
           int src_y = ysource + y;
           int dst_y = ydestination + y;
           
           if (src_y < 0 || src_y >= VRes || dst_y < 0 || dst_y >= VRes)
               continue;
           
           int src_x = xsource;
           int dst_x = xdestination;
           int remaining = width;
           
           // Bounds check for entire row
           if (src_x < 0 || src_x + width > HRes || dst_x < 0 || dst_x + width > HRes)
               goto pixel_by_pixel;
           
           // Calculate starting positions
           int src_offset = src_y * HRes + src_x;
           int dst_offset = dst_y * HRes + dst_x;
           
           int src_word_idx = src_offset / 5;
           int src_pixel_idx = src_offset % 5;
           
           int dst_word_idx = dst_offset / 5;
           int dst_pixel_idx = dst_offset % 5;
           
           // Copy leading partial word pixel-by-pixel
           while (remaining > 0 && src_pixel_idx != 0) {
               uint32_t src_word = sourceFB[src_word_idx];
               int src_shift = src_pixel_idx * 6;
               uint32_t pixel = (src_word >> src_shift) & 0x3F;
               
               int dst_shift = dst_pixel_idx * 6;
               uint32_t mask = ~(0x3F << dst_shift);
               destFB[dst_word_idx] = (destFB[dst_word_idx] & mask) | (pixel << dst_shift);
               
               src_pixel_idx++;
               dst_pixel_idx++;
               if (src_pixel_idx == 5) {
                   src_pixel_idx = 0;
                   src_word_idx++;
               }
               if (dst_pixel_idx == 5) {
                   dst_pixel_idx = 0;
                   dst_word_idx++;
               }
               remaining--;
           }
           
           // Copy complete words with memcpy
           int complete_words = remaining / 5;
           if (complete_words > 0) {
               memcpy(&destFB[dst_word_idx], &sourceFB[src_word_idx], complete_words * sizeof(uint32_t));
               src_word_idx += complete_words;
               dst_word_idx += complete_words;
               remaining -= complete_words * 5;
           }
           
           // Copy trailing partial word pixel-by-pixel
           src_pixel_idx = 0;
           dst_pixel_idx = 0;
           while (remaining > 0) {
               uint32_t src_word = sourceFB[src_word_idx];
               int src_shift = src_pixel_idx * 6;
               uint32_t pixel = (src_word >> src_shift) & 0x3F;
               
               int dst_shift = dst_pixel_idx * 6;
               uint32_t mask = ~(0x3F << dst_shift);
               destFB[dst_word_idx] = (destFB[dst_word_idx] & mask) | (pixel << dst_shift);
               
               src_pixel_idx++;
               dst_pixel_idx++;
               remaining--;
           }
           
           continue;
           
pixel_by_pixel:
           // Fall back to pixel-by-pixel for this row
           for (int x = 0; x < width; x++) {
               int sx = xsource + x;
               int dx = xdestination + x;
               
               if (sx < 0 || sx >= HRes || dx < 0 || dx >= HRes)
                   continue;
               
               int src_off = src_y * HRes + sx;
               int src_w_idx = src_off / 5;
               int src_p_idx = src_off % 5;
               
               uint32_t src_word = sourceFB[src_w_idx];
               int src_shift = src_p_idx * 6;
               uint32_t pixel = (src_word >> src_shift) & 0x3F;
               
               int dst_off = dst_y * HRes + dx;
               int dst_w_idx = dst_off / 5;
               int dst_p_idx = dst_off % 5;
               
               int dst_shift = dst_p_idx * 6;
               uint32_t mask = ~(0x3F << dst_shift);
               destFB[dst_w_idx] = (destFB[dst_w_idx] & mask) | (pixel << dst_shift);
           }
       }
   } else {
       // Misaligned - must go pixel-by-pixel
       for (int y = 0; y < height; y++) {
           int src_y = ysource + y;
           int dst_y = ydestination + y;
           
           if (src_y < 0 || src_y >= VRes || dst_y < 0 || dst_y >= VRes)
               continue;
               
           for (int x = 0; x < width; x++) {
               int src_x = xsource + x;
               int dst_x = xdestination + x;
               
               if (src_x < 0 || src_x >= HRes || dst_x < 0 || dst_x >= HRes)
                   continue;
               
               int src_offset = src_y * HRes + src_x;
               int src_word_idx = src_offset / 5;
               int src_pixel_idx = src_offset % 5;
               
               uint32_t src_word = sourceFB[src_word_idx];
               int src_shift = src_pixel_idx * 6;
               uint32_t pixel = (src_word >> src_shift) & 0x3F;
               
               int dst_offset = dst_y * HRes + dst_x;
               int dst_word_idx = dst_offset / 5;
               int dst_pixel_idx = dst_offset % 5;
               
               int dst_shift = dst_pixel_idx * 6;
               uint32_t mask = ~(0x3F << dst_shift);
               destFB[dst_word_idx] = (destFB[dst_word_idx] & mask) | (pixel << dst_shift);
           }
       }
   }
}

void blit222_same(int xsource, int ysource, int width, int height, int xdestination, int ydestination) {
   // Check if regions overlap
   int overlap = 0;
   if (xdestination < xsource + width && xdestination + width > xsource &&
       ydestination < ysource + height && ydestination + height > ysource) {
       overlap = 1;
   }
   
   // If no overlap, use the regular blit222 function
   if (!overlap) {
       // Temporarily set both source and dest to same buffer
       uint32_t *saved_src = sourceFB;
       uint32_t *saved_dst = destFB;
       sourceFB = frameBuf;
       destFB = frameBuf;
       blit222(xsource, ysource, width, height, xdestination, ydestination);
       sourceFB = saved_src;
       destFB = saved_dst;
       return;
   }
   
   // Overlapping case - need line buffer
   uint32_t lineBuffer[HRes / 5 + 1];
   
   // Determine if we need to copy top-to-bottom or bottom-to-top
   int yincrement = 1;
   int ystart = 0;
   
   if (ydestination > ysource) {
       // Copying downward - start from bottom
       yincrement = -1;
       ystart = height - 1;
   }
   
   for (int y = ystart; y >= 0 && y < height; y += yincrement) {
       int src_y = ysource + y;
       int dst_y = ydestination + y;
       
       if (src_y < 0 || src_y >= VRes || dst_y < 0 || dst_y >= VRes)
           continue;
       
       // Copy source line to buffer
       for (int x = 0; x < width; x++) {
           int src_x = xsource + x;
           
           if (src_x < 0 || src_x >= HRes)
               continue;
           
           int src_offset = src_y * HRes + src_x;
           int src_word_idx = src_offset / 5;
           int src_pixel_idx = src_offset % 5;
           
           uint32_t src_word = frameBuf[src_word_idx];
           int src_shift = src_pixel_idx * 6;
           uint32_t pixel = (src_word >> src_shift) & 0x3F;
           
           // Store in line buffer
           int buf_offset = x;
           int buf_word_idx = buf_offset / 5;
           int buf_pixel_idx = buf_offset % 5;
           int buf_shift = buf_pixel_idx * 6;
           
           if (buf_pixel_idx == 0) {
               lineBuffer[buf_word_idx] = 0;
           }
           lineBuffer[buf_word_idx] |= (pixel << buf_shift);
       }
       
       // Copy from buffer to destination line
       for (int x = 0; x < width; x++) {
           int dst_x = xdestination + x;
           
           if (dst_x < 0 || dst_x >= HRes)
               continue;
           
           // Extract from line buffer
           int buf_offset = x;
           int buf_word_idx = buf_offset / 5;
           int buf_pixel_idx = buf_offset % 5;
           int buf_shift = buf_pixel_idx * 6;
           uint32_t pixel = (lineBuffer[buf_word_idx] >> buf_shift) & 0x3F;
           
           // Write to destination
           int dst_offset = dst_y * HRes + dst_x;
           int dst_word_idx = dst_offset / 5;
           int dst_pixel_idx = dst_offset % 5;
           int dst_shift = dst_pixel_idx * 6;
           uint32_t mask = ~(0x3F << dst_shift);
           frameBuf[dst_word_idx] = (frameBuf[dst_word_idx] & mask) | (pixel << dst_shift);
       }
   }
}

// Test functions
void printRegion(uint32_t *fb, int x, int y, int w, int h, const char *label) {
   printf("\n%s (at %d,%d size %dx%d):\n", label, x, y, w, h);
   for (int row = 0; row < h && row < 10; row++) {
       printf("Row %d: ", y + row);
       for (int col = 0; col < w && col < 20; col++) {
           printf("%02X ", getPixel222(fb, x + col, y + row));
       }
       if (w > 20) printf("...");
       printf("\n");
   }
   if (h > 10) printf("...\n");
}

int verifyBlit(uint32_t *src, uint32_t *dst, int xs, int ys, int w, int h, int xd, int yd) {
   int errors = 0;
   for (int y = 0; y < h; y++) {
       for (int x = 0; x < w; x++) {
           uint8_t src_pixel = getPixel222(src, xs + x, ys + y);
           uint8_t dst_pixel = getPixel222(dst, xd + x, yd + y);
           if (src_pixel != dst_pixel) {
               if (errors < 5) {
                   printf("ERROR at dest(%d,%d): expected %02X, got %02X\n",
                          xd + x, yd + y, src_pixel, dst_pixel);
               }
               errors++;
           }
       }
   }
   return errors;
}

void test_blit222() {
   printf("\n=== Testing blit222 (between different framebuffers) ===\n");
   
   // Test 1: Aligned blit
   printf("\nTest 1: Aligned blit (both at x%%5 == 0)\n");
   memset(sourceFB, 0, FB_WORDS * sizeof(uint32_t));
   memset(destFB, 0, FB_WORDS * sizeof(uint32_t));
   
   // Create a test pattern
   for (int y = 10; y < 30; y++) {
       for (int x = 10; x < 50; x++) {
           setPixel222(sourceFB, x, y, (x + y) & 0x3F);
       }
   }
   
   blit222(10, 10, 40, 20, 100, 50);
   int errors = verifyBlit(sourceFB, destFB, 10, 10, 40, 20, 100, 50);
   printf("Result: %s (%d errors)\n", errors == 0 ? "PASS" : "FAIL", errors);
   
   // Test 2: Misaligned blit
   printf("\nTest 2: Misaligned blit (x%%5 == 1 to x%%5 == 3)\n");
   memset(sourceFB, 0, FB_WORDS * sizeof(uint32_t));
   memset(destFB, 0, FB_WORDS * sizeof(uint32_t));
   
   for (int y = 10; y < 30; y++) {
       for (int x = 11; x < 51; x++) {
           setPixel222(sourceFB, x, y, (x * 3 + y * 5) & 0x3F);
       }
   }
   
   blit222(11, 10, 40, 20, 103, 50);
   errors = verifyBlit(sourceFB, destFB, 11, 10, 40, 20, 103, 50);
   printf("Result: %s (%d errors)\n", errors == 0 ? "PASS" : "FAIL", errors);
   
   // Test 3: Partial alignment with memcpy
   printf("\nTest 3: Aligned blit with leading/trailing pixels (x%%5 == 2)\n");
   memset(sourceFB, 0, FB_WORDS * sizeof(uint32_t));
   memset(destFB, 0, FB_WORDS * sizeof(uint32_t));
   
   for (int y = 5; y < 25; y++) {
       for (int x = 2; x < 47; x++) {
           setPixel222(sourceFB, x, y, ((x ^ y) * 7) & 0x3F);
       }
   }
   
   blit222(2, 5, 45, 20, 52, 100);
   errors = verifyBlit(sourceFB, destFB, 2, 5, 45, 20, 52, 100);
   printf("Result: %s (%d errors)\n", errors == 0 ? "PASS" : "FAIL", errors);
}

void test_blit222_same() {
   printf("\n=== Testing blit222_same (within same framebuffer) ===\n");
   
   // Test 1: Non-overlapping regions
   printf("\nTest 1: Non-overlapping regions\n");
   memset(frameBuf, 0, FB_WORDS * sizeof(uint32_t));
   
   for (int y = 10; y < 30; y++) {
       for (int x = 10; x < 50; x++) {
           setPixel222(frameBuf, x, y, (x + y * 2) & 0x3F);
       }
   }
   
   // Save expected pattern
   uint8_t expected[40][20];
   for (int y = 0; y < 20; y++) {
       for (int x = 0; x < 40; x++) {
           expected[x][y] = getPixel222(frameBuf, 10 + x, 10 + y);
       }
   }
   
   blit222_same(10, 10, 40, 20, 100, 100);
   
   int errors = 0;
   for (int y = 0; y < 20; y++) {
       for (int x = 0; x < 40; x++) {
           if (getPixel222(frameBuf, 100 + x, 100 + y) != expected[x][y]) {
               errors++;
           }
       }
   }
   printf("Result: %s (%d errors)\n", errors == 0 ? "PASS" : "FAIL", errors);
   
   // Test 2: Overlapping - shift right
   printf("\nTest 2: Overlapping regions - shift right\n");
   memset(frameBuf, 0, FB_WORDS * sizeof(uint32_t));
   
   for (int y = 10; y < 30; y++) {
       for (int x = 10; x < 50; x++) {
           setPixel222(frameBuf, x, y, (x * 3 + y) & 0x3F);
       }
   }
   
   // Save expected pattern
   for (int y = 0; y < 20; y++) {
       for (int x = 0; x < 40; x++) {
           expected[x][y] = getPixel222(frameBuf, 10 + x, 10 + y);
       }
   }
   
   blit222_same(10, 10, 40, 20, 30, 10);  // Shift right by 20 pixels
   
   errors = 0;
   for (int y = 0; y < 20; y++) {
       for (int x = 0; x < 40; x++) {
           if (getPixel222(frameBuf, 30 + x, 10 + y) != expected[x][y]) {
               errors++;
           }
       }
   }
   printf("Result: %s (%d errors)\n", errors == 0 ? "PASS" : "FAIL", errors);
   
   // Test 3: Overlapping - shift down
   printf("\nTest 3: Overlapping regions - shift down\n");
   memset(frameBuf, 0, FB_WORDS * sizeof(uint32_t));
   
   for (int y = 10; y < 30; y++) {
       for (int x = 10; x < 50; x++) {
           setPixel222(frameBuf, x, y, (x + y * 5) & 0x3F);
       }
   }
   
   // Save expected pattern
   for (int y = 0; y < 20; y++) {
       for (int x = 0; x < 40; x++) {
           expected[x][y] = getPixel222(frameBuf, 10 + x, 10 + y);
       }
   }
   
   blit222_same(10, 10, 40, 20, 10, 20);  // Shift down by 10 pixels
   
   errors = 0;
   for (int y = 0; y < 20; y++) {
       for (int x = 0; x < 40; x++) {
           if (getPixel222(frameBuf, 10 + x, 20 + y) != expected[x][y]) {
               errors++;
           }
       }
   }
   printf("Result: %s (%d errors)\n", errors == 0 ? "PASS" : "FAIL", errors);
   
   // Test 4: Overlapping - shift up
   printf("\nTest 4: Overlapping regions - shift up\n");
   memset(frameBuf, 0, FB_WORDS * sizeof(uint32_t));
   
   for (int y = 20; y < 40; y++) {
       for (int x = 10; x < 50; x++) {
           setPixel222(frameBuf, x, y, (x * 2 + y * 3) & 0x3F);
       }
   }
   
   // Save expected pattern
   for (int y = 0; y < 20; y++) {
       for (int x = 0; x < 40; x++) {
           expected[x][y] = getPixel222(frameBuf, 10 + x, 20 + y);
       }
   }
   
   blit222_same(10, 20, 40, 20, 10, 10);  // Shift up by 10 pixels
   
   errors = 0;
   for (int y = 0; y < 20; y++) {
       for (int x = 0; x < 40; x++) {
           if (getPixel222(frameBuf, 10 + x, 10 + y) != expected[x][y]) {
               errors++;
           }
       }
   }
   printf("Result: %s (%d errors)\n", errors == 0 ? "PASS" : "FAIL", errors);
}

int main() {
   // Allocate framebuffers
   sourceFB = calloc(FB_WORDS, sizeof(uint32_t));
   destFB = calloc(FB_WORDS, sizeof(uint32_t));
   frameBuf = calloc(FB_WORDS, sizeof(uint32_t));
   
   if (!sourceFB || !destFB || !frameBuf) {
       printf("Failed to allocate framebuffers\n");
       return 1;
   }
   
   printf("Framebuffer test program\n");
   printf("Resolution: %dx%d\n", HRES, VRES);
   printf("Framebuffer size: %d words (%d bytes)\n", FB_WORDS, FB_WORDS * 4);
   
   test_blit222();
   test_blit222_same();
   
   printf("\n=== All tests complete ===\n");
   
   free(sourceFB);
   free(destFB);
   free(frameBuf);
   
   return 0;
}

Edited 2025-11-14 05:34 by matherp
 
Volhout
Guru

Joined: 05/03/2018
Location: Netherlands
Posts: 5464
Posted: 07:57pm 13 Nov 2025
Copy link to clipboard 
Print this post

Wauw...

You 2 are a good team.

Volhout
PicomiteVGA PETSCII ROBOTS
 
Mixtel90

Guru

Joined: 05/10/2019
Location: United Kingdom
Posts: 8293
Posted: 08:27am 14 Nov 2025
Copy link to clipboard 
Print this post

It's not just the AI, it's knowing what to ask and how to frame your question.
Very impressive. :)
Mick

Zilog Inside! nascom.info for Nascom & Gemini
Preliminary MMBasic docs & my PCB designs
 
matherp
Guru

Joined: 11/12/2012
Location: United Kingdom
Posts: 10583
Posted: 08:32am 14 Nov 2025
Copy link to clipboard 
Print this post

Harm

My current thinking is as follows:
FLASH LOAD IMAGE flashslotno, filename$ [,format]

This loads a RGB888 .BMP image into a flash slot in the format specified - default RGB121. The image can be as large as you can get in the flash slot and is not limited by MM.HRES/MM.VRES
The firmware will note the physical size of the image in the flash slot= ImageHres,ImageVres

then

BLIT FLASH xflash, yflash, width, height, xframebuffer, yframebuffer [,transparentcolour]

This will copy a sub section of the flash image into a framebuffer (not the physical screen except where that is also in RGB121).
The user will of course need to know where in the flash image the elements they need are. However, by not restricting the flash image to be the size of the framebuffer, this gives great flexibility. So for example the image could be 1 sprite high and n sprites wide.
Pixels in the optional transparent colour would not be copied

To keep things simple the first implementation would be RGB121 only.
Edited 2025-11-14 18:33 by matherp
 
JohnS
Guru

Joined: 18/11/2011
Location: United Kingdom
Posts: 4142
Posted: 09:01am 14 Nov 2025
Copy link to clipboard 
Print this post

A good team - indeed.

John
 
matherp
Guru

Joined: 11/12/2012
Location: United Kingdom
Posts: 10583
Posted: 09:54am 14 Nov 2025
Copy link to clipboard 
Print this post

The key is trying to be as precise as possible with the instruction:

Me:
  Quote  write the following C function:

void blit121(uint8_t *source, uint8_t *destination, int xsource, int ysource, int width, int height, int xdesination, int ydestination, int missingcolour);

I have 2 RGB121 framebuffers. The source framebuffer at *source is HResS by VResS pixels. The destination framebuffer at *destination is HResD by VResD pixels. Pixels are packed 2 to a byte . Write a C function that will take an area defined by xsource,ysource,width and height from the source framebuffer and write it to an area also width by height but at xdestination,ydestination in the other.
If missing colour is in the range 0 to 15 then do not copy pixels from the source to the destination if the source RGB121 colour matches missing colour. if missingcolour is outside of this range then copy all pixels
The rectangle defined by xsource,ysource,width, height is guaranteed to the be fully contained in the source framebuffer. The rectangle in the destination framebuffer may overlap the framebuffer. In this case any pixels outside of the drawing area should be ignored in the copy.
Optimise the copy when (xsource % 2) == (xdestination % 2) && !(missingcolour>=0 && missingcolour<=15)


Claude:
  Quote  Here's the optimized blit121 function for RGB121 framebuffers:
Code returned here
Key features:

Clipping: Handles destination rectangles that extend beyond framebuffer boundaries by clipping to valid regions
Transparency: Supports transparent color (0-15) where matching pixels aren't copied
Optimized aligned path: When source and destination have the same pixel alignment (both even or both odd) AND there's no transparency, it copies whole bytes at once instead of individual pixels
Pixel packing: Correctly handles RGB121 format with 2 pixels per byte (even pixel in upper nibble, odd pixel in lower nibble)
Partial byte handling: In the fast path, properly handles first/last bytes that may only be partially within the copy region

The function assumes HResS, VResS, HResD, and VResD are defined globally or as macros representing the framebuffer dimensions.


Me:
  Quote  write a comprehensive test harness to validate the function


Claude:
  Quote  Here's a comprehensive test harness for the blit121 function:
Code below
This comprehensive test harness includes:

Basic Copy Tests: Simple rectangular copies without transparency or clipping
Alignment Tests: All four alignment combinations (even-even, odd-odd, even-odd, odd-even)
Clipping Tests: Left, right, top, bottom edge clipping and complete out-of-bounds
Transparency Tests: Various transparent colors (0, 8, 15)
Edge Cases: Single pixel, single row, single column, odd widths
Pattern Preservation: Complex patterns to verify pixel-perfect copying
Combined Tests: Transparency with clipping
Color Range: All 16 possible RGB121 colors
Large Area: Performance testing with large rectangles
Boundary Alignments: Various byte boundary crossing scenarios

The test harness provides clear pass/fail indicators and detailed mismatch reporting when tests fail.


Code run on https://www.programiz.com/c-programming/online-compiler/

Result:
  Quote  ========================================
  blit121 Comprehensive Test Suite
========================================
Source framebuffer: 320x240
Destination framebuffer: 320x240

=== Test 1: Basic Copy ===
 ✓ PASS: Basic copy 20x15

=== Test 2: Alignment Cases ===
 ✓ PASS: Even to even (aligned)
 ✓ PASS: Odd to odd (aligned)
 ✓ PASS: Even to odd (misaligned)
 ✓ PASS: Odd to even (misaligned)

=== Test 3: Clipping ===
 ✓ PASS: Clip left edge
 ✓ PASS: Clip right edge
 ✓ PASS: Clip top edge
 ✓ PASS: Clip bottom edge
 ✓ PASS: Completely outside (no-op)

=== Test 4: Transparency ===
 ✓ PASS: Transparent color 0
 ✓ PASS: Transparent color 15
 ✓ PASS: Transparent color 8

=== Test 5: Edge Cases ===
 ✓ PASS: Single pixel copy
 ✓ PASS: Single row
 ✓ PASS: Single column
 ✓ PASS: Odd width (33 pixels)

=== Test 6: Pattern Preservation ===
 ✓ PASS: XOR pattern preservation

=== Test 7: Transparency with Clipping ===
 ✓ PASS: Transparency with clipping

=== Test 8: All Colors ===
 ✓ PASS: All 16 colors

=== Test 9: Large Area Copy ===
 ✓ PASS: Large area (200x150)

=== Test 10: Boundary Alignments ===
 ✓ PASS: Boundary alignment variations

========================================
  Test Results Summary
========================================
Total tests:  22
Passed:       22 ✓
Failed:       0 ✗
Success rate: 100.0%
========================================


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>

// Define framebuffer dimensions
#define HResS 320
#define VResS 240
#define HResD 320
#define VResD 240

// ============================================================================
// BLIT121 FUNCTION
// ==================================================
void blit121(uint8_t *source, uint8_t *destination, int xsource, int ysource,
            int width, int height, int xdestination, int ydestination, int missingcolour) {
   
   // Check if we have a valid missing colour (transparency)
   int has_transparency = (missingcolour >= 0 && missingcolour <= 15);
   
   // Calculate source and destination row starts
   int src_offset_x = xsource >> 1;  // xsource / 2
   int dst_offset_x = xdestination >> 1;  // xdestination / 2
   
   // Check for alignment optimization opportunity
   int aligned = ((xsource & 1) == (xdestination & 1)) && !has_transparency;
   
   // Determine clipping boundaries for destination
   int start_x = 0;
   int start_y = 0;
   int end_x = width;
   int end_y = height;
   
   // Clip to destination framebuffer bounds
   if (xdestination < 0) {
       start_x = -xdestination;
   }
   if (ydestination < 0) {
       start_y = -ydestination;
   }
   if (xdestination + width > HResD) {
       end_x = HResD - xdestination;
   }
   if (ydestination + height > VResD) {
       end_y = VResD - ydestination;
   }
   
   // Nothing to draw if completely clipped
   if (start_x >= end_x || start_y >= end_y) {
       return;
   }
   
   int src_stride = (HResS + 1) >> 1;  // bytes per row in source
   int dst_stride = (HResD + 1) >> 1;  // bytes per row in destination
   
   if (aligned) {
       // Fast path: aligned copy with no transparency
       
       // Determine if first pixel is odd and last pixel is even
       int first_src_x = xsource + start_x;
       int last_src_x = xsource + end_x - 1;
       int first_pixel_odd = first_src_x & 1;
       int last_pixel_even = !(last_src_x & 1);
       
       // Calculate byte indices for the clipped region
       int first_byte = (first_src_x) >> 1;
       int last_byte = (last_src_x) >> 1;
       
       int first_dst_x = xdestination + start_x;
       int first_dst_byte = first_dst_x >> 1;
       
       for (int y = start_y; y < end_y; y++) {
           int src_row_base = (ysource + y) * src_stride;
           int dst_row_base = (ydestination + y) * dst_stride;
           
           uint8_t *src_row = source + src_row_base;
           uint8_t *dst_row = destination + dst_row_base;
           
           if (first_byte == last_byte) {
               // All pixels are in the same byte
               if (first_pixel_odd && last_pixel_even) {
                   // Both nibbles
                   dst_row[first_dst_byte] = src_row[first_byte];
               } else if (first_pixel_odd) {
                   // Only lower nibble
                   dst_row[first_dst_byte] = (dst_row[first_dst_byte] & 0xF0) | (src_row[first_byte] & 0x0F);
               } else {
                   // Only upper nibble
                   dst_row[first_dst_byte] = (dst_row[first_dst_byte] & 0x0F) | (src_row[first_byte] & 0xF0);
               }
           } else {
               int byte_idx = 0;
               
               // Handle partial first byte if we start on an odd pixel
               if (first_pixel_odd) {
                   dst_row[first_dst_byte] = (dst_row[first_dst_byte] & 0xF0) | (src_row[first_byte] & 0x0F);
                   byte_idx = 1;
               }
               
               // Copy complete bytes
               int num_full_bytes = last_byte - first_byte + 1 - byte_idx - (last_pixel_even ? 1 : 0);
               for (int i = 0; i < num_full_bytes; i++) {
                   dst_row[first_dst_byte + byte_idx + i] = src_row[first_byte + byte_idx + i];
               }
               
               // Handle partial last byte if we end on an even pixel
               if (last_pixel_even) {
                   int last_offset = last_byte - first_byte;
                   dst_row[first_dst_byte + last_offset] = (dst_row[first_dst_byte + last_offset] & 0x0F) | (src_row[last_byte] & 0xF0);
               }
           }
       }
   } else {
       // Slow path: pixel-by-pixel copy
       for (int y = start_y; y < end_y; y++) {
           for (int x = start_x; x < end_x; x++) {
               int src_x = xsource + x;
               int src_y = ysource + y;
               int dst_x = xdestination + x;
               int dst_y = ydestination + y;
               
               // Get source pixel
               int src_byte_idx = src_y * src_stride + (src_x >> 1);
               uint8_t src_byte = source[src_byte_idx];
               uint8_t src_pixel;
               
               if (src_x & 1) {
                   src_pixel = src_byte & 0x0F;  // Odd pixel (lower nibble)
               } else {
                   src_pixel = (src_byte >> 4) & 0x0F;  // Even pixel (upper nibble)
               }
               
               // Check transparency
               if (has_transparency && src_pixel == missingcolour) {
                   continue;  // Skip this pixel
               }
               
               // Write to destination
               int dst_byte_idx = dst_y * dst_stride + (dst_x >> 1);
               
               if (dst_x & 1) {
                   // Odd pixel (lower nibble)
                   destination[dst_byte_idx] = (destination[dst_byte_idx] & 0xF0) | src_pixel;
               } else {
                   // Even pixel (upper nibble)
                   destination[dst_byte_idx] = (destination[dst_byte_idx] & 0x0F) | (src_pixel << 4);
               }
           }
       }
   }
}
// ============================================================================
// TEST HARNESS
// ============================================================================

// Helper functions for testing

// Get a pixel from a framebuffer
uint8_t get_pixel(uint8_t *fb, int x, int y, int hres) {
   int stride = (hres + 1) >> 1;
   int byte_idx = y * stride + (x >> 1);
   uint8_t byte_val = fb[byte_idx];
   
   if (x & 1) {
       return byte_val & 0x0F;  // Odd pixel
   } else {
       return (byte_val >> 4) & 0x0F;  // Even pixel
   }
}

// Set a pixel in a framebuffer
void set_pixel(uint8_t *fb, int x, int y, int hres, uint8_t color) {
   int stride = (hres + 1) >> 1;
   int byte_idx = y * stride + (x >> 1);
   
   if (x & 1) {
       fb[byte_idx] = (fb[byte_idx] & 0xF0) | (color & 0x0F);
   } else {
       fb[byte_idx] = (fb[byte_idx] & 0x0F) | ((color & 0x0F) << 4);
   }
}

// Fill a rectangle with a color
void fill_rect(uint8_t *fb, int x, int y, int width, int height, int hres, int vres, uint8_t color) {
   for (int dy = 0; dy < height; dy++) {
       for (int dx = 0; dx < width; dx++) {
           if (x + dx >= 0 && x + dx < hres && y + dy >= 0 && y + dy < vres) {
               set_pixel(fb, x + dx, y + dy, hres, color);
           }
       }
   }
}

// Fill entire framebuffer with a color
void clear_fb(uint8_t *fb, int hres, int vres, uint8_t color) {
   int size = ((hres + 1) >> 1) * vres;
   uint8_t fill_byte = (color << 4) | color;
   memset(fb, fill_byte, size);
}

// Create a pattern in framebuffer
void create_pattern(uint8_t *fb, int hres, int vres) {
   for (int y = 0; y < vres; y++) {
       for (int x = 0; x < hres; x++) {
           uint8_t color = ((x / 8) + (y / 8)) & 0x0F;
           set_pixel(fb, x, y, hres, color);
       }
   }
}

// Print a small area of framebuffer (for debugging)
void print_fb_area(uint8_t *fb, int x, int y, int width, int height, int hres, const char *label) {
   printf("%s:\n", label);
   for (int dy = 0; dy < height && dy < 16; dy++) {
       printf("  ");
       for (int dx = 0; dx < width && dx < 32; dx++) {
           if (x + dx >= 0 && x + dx < hres && y + dy >= 0) {
               printf("%X", get_pixel(fb, x + dx, y + dy, hres));
           } else {
               printf(".");
           }
       }
       printf("\n");
   }
}

// Compare two framebuffers in a region
bool compare_region(uint8_t *fb1, uint8_t *fb2, int x, int y, int width, int height, int hres) {
   for (int dy = 0; dy < height; dy++) {
       for (int dx = 0; dx < width; dx++) {
           uint8_t p1 = get_pixel(fb1, x + dx, y + dy, hres);
           uint8_t p2 = get_pixel(fb2, x + dx, y + dy, hres);
           if (p1 != p2) {
               printf("    Mismatch at (%d,%d): expected %X, got %X\n",
                      x + dx, y + dy, p1, p2);
               return false;
           }
       }
   }
   return true;
}

// Test result tracking
typedef struct {
   int total;
   int passed;
   int failed;
} TestResults;

void init_results(TestResults *r) {
   r->total = 0;
   r->passed = 0;
   r->failed = 0;
}

void record_test(TestResults *r, bool passed, const char *test_name) {
   r->total++;
   if (passed) {
       r->passed++;
       printf("  ✓ PASS: %s\n", test_name);
   } else {
       r->failed++;
       printf("  ✗ FAIL: %s\n", test_name);
   }
}

// Allocate framebuffers
uint8_t* alloc_fb(int hres, int vres) {
   int size = ((hres + 1) >> 1) * vres;
   return (uint8_t*)malloc(size);
}

// Test 1: Basic copy - no clipping, no transparency
void test_basic_copy(TestResults *r) {
   printf("\n=== Test 1: Basic Copy ===\n");
   
   uint8_t *src = alloc_fb(HResS, VResS);
   uint8_t *dst = alloc_fb(HResD, VResD);
   uint8_t *expected = alloc_fb(HResD, VResD);
   
   clear_fb(src, HResS, VResS, 5);
   clear_fb(dst, HResD, VResD, 10);
   clear_fb(expected, HResD, VResD, 10);
   
   fill_rect(src, 10, 10, 20, 15, HResS, VResS, 7);
   fill_rect(expected, 50, 50, 20, 15, HResD, VResD, 7);
   
   blit121(src, dst, 10, 10, 20, 15, 50, 50, -1);
   
   bool passed = compare_region(expected, dst, 0, 0, HResD, VResD, HResD);
   record_test(r, passed, "Basic copy 20x15");
   
   free(src);
   free(dst);
   free(expected);
}

// Test 2: Alignment cases
void test_alignment(TestResults *r) {
   printf("\n=== Test 2: Alignment Cases ===\n");
   
   uint8_t *src = alloc_fb(HResS, VResS);
   uint8_t *dst = alloc_fb(HResD, VResD);
   uint8_t *expected = alloc_fb(HResD, VResD);
   
   // Test 2a: Even to even (aligned)
   clear_fb(src, HResS, VResS, 3);
   clear_fb(dst, HResD, VResD, 8);
   clear_fb(expected, HResD, VResD, 8);
   
   fill_rect(src, 20, 20, 40, 30, HResS, VResS, 12);
   fill_rect(expected, 100, 100, 40, 30, HResD, VResD, 12);
   
   blit121(src, dst, 20, 20, 40, 30, 100, 100, -1);
   bool passed_2a = compare_region(expected, dst, 0, 0, HResD, VResD, HResD);
   record_test(r, passed_2a, "Even to even (aligned)");
   
   // Test 2b: Odd to odd (aligned)
   clear_fb(src, HResS, VResS, 3);
   clear_fb(dst, HResD, VResD, 8);
   clear_fb(expected, HResD, VResD, 8);
   
   fill_rect(src, 21, 21, 40, 30, HResS, VResS, 12);
   fill_rect(expected, 101, 101, 40, 30, HResD, VResD, 12);
   
   blit121(src, dst, 21, 21, 40, 30, 101, 101, -1);
   bool passed_2b = compare_region(expected, dst, 0, 0, HResD, VResD, HResD);
   record_test(r, passed_2b, "Odd to odd (aligned)");
   
   // Test 2c: Even to odd (misaligned)
   clear_fb(src, HResS, VResS, 3);
   clear_fb(dst, HResD, VResD, 8);
   clear_fb(expected, HResD, VResD, 8);
   
   fill_rect(src, 20, 20, 40, 30, HResS, VResS, 12);
   fill_rect(expected, 101, 101, 40, 30, HResD, VResD, 12);
   
   blit121(src, dst, 20, 20, 40, 30, 101, 101, -1);
   bool passed_2c = compare_region(expected, dst, 0, 0, HResD, VResD, HResD);
   record_test(r, passed_2c, "Even to odd (misaligned)");
   
   // Test 2d: Odd to even (misaligned)
   clear_fb(src, HResS, VResS, 3);
   clear_fb(dst, HResD, VResD, 8);
   clear_fb(expected, HResD, VResD, 8);
   
   fill_rect(src, 21, 21, 40, 30, HResS, VResS, 12);
   fill_rect(expected, 100, 100, 40, 30, HResD, VResD, 12);
   
   blit121(src, dst, 21, 21, 40, 30, 100, 100, -1);
   bool passed_2d = compare_region(expected, dst, 0, 0, HResD, VResD, HResD);
   record_test(r, passed_2d, "Odd to even (misaligned)");
   
   free(src);
   free(dst);
   free(expected);
}

// Test 3: Clipping tests
void test_clipping(TestResults *r) {
   printf("\n=== Test 3: Clipping ===\n");
   
   uint8_t *src = alloc_fb(HResS, VResS);
   uint8_t *dst = alloc_fb(HResD, VResD);
   uint8_t *expected = alloc_fb(HResD, VResD);
   
   // Test 3a: Clip left
   clear_fb(src, HResS, VResS, 2);
   clear_fb(dst, HResD, VResD, 9);
   clear_fb(expected, HResD, VResD, 9);
   
   fill_rect(src, 10, 10, 30, 20, HResS, VResS, 6);
   fill_rect(expected, 0, 50, 20, 20, HResD, VResD, 6);
   
   blit121(src, dst, 10, 10, 30, 20, -10, 50, -1);
   bool passed_3a = compare_region(expected, dst, 0, 0, HResD, VResD, HResD);
   record_test(r, passed_3a, "Clip left edge");
   
   // Test 3b: Clip right
   clear_fb(src, HResS, VResS, 2);
   clear_fb(dst, HResD, VResD, 9);
   clear_fb(expected, HResD, VResD, 9);
   
   fill_rect(src, 10, 10, 30, 20, HResS, VResS, 6);
   fill_rect(expected, HResD - 20, 50, 20, 20, HResD, VResD, 6);
   
   blit121(src, dst, 10, 10, 30, 20, HResD - 20, 50, -1);
   bool passed_3b = compare_region(expected, dst, 0, 0, HResD, VResD, HResD);
   record_test(r, passed_3b, "Clip right edge");
   
   // Test 3c: Clip top
   clear_fb(src, HResS, VResS, 2);
   clear_fb(dst, HResD, VResD, 9);
   clear_fb(expected, HResD, VResD, 9);
   
   fill_rect(src, 10, 10, 30, 20, HResS, VResS, 6);
   fill_rect(expected, 50, 0, 30, 10, HResD, VResD, 6);
   
   blit121(src, dst, 10, 10, 30, 20, 50, -10, -1);
   bool passed_3c = compare_region(expected, dst, 0, 0, HResD, VResD, HResD);
   record_test(r, passed_3c, "Clip top edge");
   
   // Test 3d: Clip bottom
   clear_fb(src, HResS, VResS, 2);
   clear_fb(dst, HResD, VResD, 9);
   clear_fb(expected, HResD, VResD, 9);
   
   fill_rect(src, 10, 10, 30, 20, HResS, VResS, 6);
   fill_rect(expected, 50, VResD - 10, 30, 10, HResD, VResD, 6);
   
   blit121(src, dst, 10, 10, 30, 20, 50, VResD - 10, -1);
   bool passed_3d = compare_region(expected, dst, 0, 0, HResD, VResD, HResD);
   record_test(r, passed_3d, "Clip bottom edge");
   
   // Test 3e: Completely outside (should do nothing)
   clear_fb(dst, HResD, VResD, 9);
   clear_fb(expected, HResD, VResD, 9);
   
   blit121(src, dst, 10, 10, 30, 20, HResD + 100, 50, -1);
   bool passed_3e = compare_region(expected, dst, 0, 0, HResD, VResD, HResD);
   record_test(r, passed_3e, "Completely outside (no-op)");
   
   free(src);
   free(dst);
   free(expected);
}

// Test 4: Transparency
void test_transparency(TestResults *r) {
   printf("\n=== Test 4: Transparency ===\n");
   
   uint8_t *src = alloc_fb(HResS, VResS);
   uint8_t *dst = alloc_fb(HResD, VResD);
   uint8_t *expected = alloc_fb(HResD, VResD);
   
   // Test 4a: Transparent color 0
   clear_fb(src, HResS, VResS, 0);
   clear_fb(dst, HResD, VResD, 15);
   clear_fb(expected, HResD, VResD, 15);
   
   // Create a checkerboard pattern with color 0 and color 5
   for (int y = 10; y < 30; y++) {
       for (int x = 10; x < 30; x++) {
           uint8_t color = ((x + y) & 1) ? 5 : 0;
           set_pixel(src, x, y, HResS, color);
           // Only color 5 should be copied
           if (color == 5) {
               set_pixel(expected, x + 40, y + 40, HResD, color);
           }
       }
   }
   
   blit121(src, dst, 10, 10, 20, 20, 50, 50, 0);
   bool passed_4a = compare_region(expected, dst, 0, 0, HResD, VResD, HResD);
   record_test(r, passed_4a, "Transparent color 0");
   
   // Test 4b: Transparent color 15
   clear_fb(src, HResS, VResS, 15);
   clear_fb(dst, HResD, VResD, 3);
   clear_fb(expected, HResD, VResD, 3);
   
   for (int y = 10; y < 30; y++) {
       for (int x = 10; x < 30; x++) {
           uint8_t color = ((x + y) & 1) ? 7 : 15;
           set_pixel(src, x, y, HResS, color);
           if (color == 7) {
               set_pixel(expected, x + 40, y + 40, HResD, color);
           }
       }
   }
   
   blit121(src, dst, 10, 10, 20, 20, 50, 50, 15);
   bool passed_4b = compare_region(expected, dst, 0, 0, HResD, VResD, HResD);
   record_test(r, passed_4b, "Transparent color 15");
   
   // Test 4c: Mid-range transparent color
   clear_fb(src, HResS, VResS, 8);
   clear_fb(dst, HResD, VResD, 1);
   clear_fb(expected, HResD, VResD, 1);
   
   for (int y = 10; y < 30; y++) {
       for (int x = 10; x < 30; x++) {
           uint8_t color = ((x + y) & 1) ? 12 : 8;
           set_pixel(src, x, y, HResS, color);
           if (color == 12) {
               set_pixel(expected, x + 40, y + 40, HResD, color);
           }
       }
   }
   
   blit121(src, dst, 10, 10, 20, 20, 50, 50, 8);
   bool passed_4c = compare_region(expected, dst, 0, 0, HResD, VResD, HResD);
   record_test(r, passed_4c, "Transparent color 8");
   
   free(src);
   free(dst);
   free(expected);
}

// Test 5: Edge cases
void test_edge_cases(TestResults *r) {
   printf("\n=== Test 5: Edge Cases ===\n");
   
   uint8_t *src = alloc_fb(HResS, VResS);
   uint8_t *dst = alloc_fb(HResD, VResD);
   uint8_t *expected = alloc_fb(HResD, VResD);
   
   // Test 5a: Single pixel copy
   clear_fb(src, HResS, VResS, 0);
   clear_fb(dst, HResD, VResD, 0);
   clear_fb(expected, HResD, VResD, 0);
   
   set_pixel(src, 50, 50, HResS, 14);
   set_pixel(expected, 100, 100, HResD, 14);
   
   blit121(src, dst, 50, 50, 1, 1, 100, 100, -1);
   bool passed_5a = compare_region(expected, dst, 0, 0, HResD, VResD, HResD);
   record_test(r, passed_5a, "Single pixel copy");
   
   // Test 5b: Single row
   clear_fb(src, HResS, VResS, 0);
   clear_fb(dst, HResD, VResD, 0);
   clear_fb(expected, HResD, VResD, 0);
   
   fill_rect(src, 10, 50, 50, 1, HResS, VResS, 9);
   fill_rect(expected, 20, 100, 50, 1, HResD, VResD, 9);
   
   blit121(src, dst, 10, 50, 50, 1, 20, 100, -1);
   bool passed_5b = compare_region(expected, dst, 0, 0, HResD, VResD, HResD);
   record_test(r, passed_5b, "Single row");
   
   // Test 5c: Single column
   clear_fb(src, HResS, VResS, 0);
   clear_fb(dst, HResD, VResD, 0);
   clear_fb(expected, HResD, VResD, 0);
   
   fill_rect(src, 50, 10, 1, 50, HResS, VResS, 11);
   fill_rect(expected, 100, 20, 1, 50, HResD, VResD, 11);
   
   blit121(src, dst, 50, 10, 1, 50, 100, 20, -1);
   bool passed_5c = compare_region(expected, dst, 0, 0, HResD, VResD, HResD);
   record_test(r, passed_5c, "Single column");
   
   // Test 5d: Odd width (tests partial byte handling)
   clear_fb(src, HResS, VResS, 1);
   clear_fb(dst, HResD, VResD, 1);
   clear_fb(expected, HResD, VResD, 1);
   
   fill_rect(src, 20, 20, 33, 25, HResS, VResS, 13);
   fill_rect(expected, 80, 80, 33, 25, HResD, VResD, 13);
   
   blit121(src, dst, 20, 20, 33, 25, 80, 80, -1);
   bool passed_5d = compare_region(expected, dst, 0, 0, HResD, VResD, HResD);
   record_test(r, passed_5d, "Odd width (33 pixels)");
   
   free(src);
   free(dst);
   free(expected);
}

// Test 6: Pattern preservation
void test_pattern_preservation(TestResults *r) {
   printf("\n=== Test 6: Pattern Preservation ===\n");
   
   uint8_t *src = alloc_fb(HResS, VResS);
   uint8_t *dst = alloc_fb(HResD, VResD);
   uint8_t *expected = alloc_fb(HResD, VResD);
   
   clear_fb(dst, HResD, VResD, 0);
   clear_fb(expected, HResD, VResD, 0);
   
   // Create a detailed pattern
   for (int y = 0; y < 40; y++) {
       for (int x = 0; x < 40; x++) {
           uint8_t color = (x ^ y) & 0x0F;
           set_pixel(src, x + 10, y + 10, HResS, color);
           set_pixel(expected, x + 100, y + 100, HResD, color);
       }
   }
   
   blit121(src, dst, 10, 10, 40, 40, 100, 100, -1);
   bool passed = compare_region(expected, dst, 0, 0, HResD, VResD, HResD);
   record_test(r, passed, "XOR pattern preservation");
   
   free(src);
   free(dst);
   free(expected);
}

// Test 7: Transparency with clipping
void test_transparency_with_clipping(TestResults *r) {
   printf("\n=== Test 7: Transparency with Clipping ===\n");
   
   uint8_t *src = alloc_fb(HResS, VResS);
   uint8_t *dst = alloc_fb(HResD, VResD);
   uint8_t *expected = alloc_fb(HResD, VResD);
   
   clear_fb(dst, HResD, VResD, 7);
   clear_fb(expected, HResD, VResD, 7);
   
   // Create checkerboard with transparent color
   for (int y = 0; y < 50; y++) {
       for (int x = 0; x < 50; x++) {
           uint8_t color = ((x + y) & 1) ? 4 : 0;
           set_pixel(src, x + 10, y + 10, HResS, color);
       }
   }
   
   // Simulate clipping: destination starts at -10, -10
   // So only pixels from source (10,10) should be visible at dest (0,0)
   for (int y = 0; y < 40; y++) {
       for (int x = 0; x < 40; x++) {
           int src_x = x + 20;
           int src_y = y + 20;
           uint8_t color = ((src_x + src_y) & 1) ? 4 : 0;
           if (color != 0) {
               set_pixel(expected, x, y, HResD, color);
           }
       }
   }
   
   blit121(src, dst, 10, 10, 50, 50, -10, -10, 0);
   bool passed = compare_region(expected, dst, 0, 0, 50, 50, HResD);
   record_test(r, passed, "Transparency with clipping");
   
   free(src);
   free(dst);
   free(expected);
}

// Test 8: All colors
void test_all_colors(TestResults *r) {
   printf("\n=== Test 8: All Colors ===\n");
   
   uint8_t *src = alloc_fb(HResS, VResS);
   uint8_t *dst = alloc_fb(HResD, VResD);
   uint8_t *expected = alloc_fb(HResD, VResD);
   
   clear_fb(dst, HResD, VResD, 0);
   clear_fb(expected, HResD, VResD, 0);
   
   // Create a pattern using all 16 colors
   for (int color = 0; color < 16; color++) {
       for (int x = 0; x < 8; x++) {
           for (int y = 0; y < 8; y++) {
               set_pixel(src, 10 + color * 8 + x, 10 + y, HResS, color);
               set_pixel(expected, 50 + color * 8 + x, 50 + y, HResD, color);
           }
       }
   }
   
   blit121(src, dst, 10, 10, 128, 8, 50, 50, -1);
   bool passed = compare_region(expected, dst, 0, 0, HResD, VResD, HResD);
   record_test(r, passed, "All 16 colors");
   
   free(src);
   free(dst);
   free(expected);
}

// Test 9: Large area copy
void test_large_area(TestResults *r) {
   printf("\n=== Test 9: Large Area Copy ===\n");
   
   uint8_t *src = alloc_fb(HResS, VResS);
   uint8_t *dst = alloc_fb(HResD, VResD);
   uint8_t *expected = alloc_fb(HResD, VResD);
   
   clear_fb(dst, HResD, VResD, 2);
   clear_fb(expected, HResD, VResD, 2);
   
   create_pattern(src, HResS, VResS);
   
   // Copy a large area
   for (int y = 0; y < 150; y++) {
       for (int x = 0; x < 200; x++) {
           uint8_t color = get_pixel(src, x + 10, y + 10, HResS);
           set_pixel(expected, x + 50, y + 40, HResD, color);
       }
   }
   
   blit121(src, dst, 10, 10, 200, 150, 50, 40, -1);
   bool passed = compare_region(expected, dst, 0, 0, HResD, VResD, HResD);
   record_test(r, passed, "Large area (200x150)");
   
   free(src);
   free(dst);
   free(expected);
}

// Test 10: Boundary alignment tests
void test_boundary_alignments(TestResults *r) {
   printf("\n=== Test 10: Boundary Alignments ===\n");
   
   uint8_t *src = alloc_fb(HResS, VResS);
   uint8_t *dst = alloc_fb(HResD, VResD);
   uint8_t *expected = alloc_fb(HResD, VResD);
   
   // Test various byte boundary crossings
   int test_cases[][4] = {
       {0, 0, 17, 10},    // Start at even, odd width
       {1, 0, 17, 10},    // Start at odd, odd width
       {0, 0, 16, 10},    // Start at even, even width
       {1, 0, 16, 10},    // Start at odd, even width
       {7, 0, 18, 10},    // Start near byte boundary
       {15, 0, 18, 10},   // Start at odd byte boundary
   };
   
   bool all_passed = true;
   for (int i = 0; i < 6; i++) {
       clear_fb(src, HResS, VResS, 1);
       clear_fb(dst, HResD, VResD, 1);
       clear_fb(expected, HResD, VResD, 1);
       
       int sx = test_cases[i][0];
       int sy = test_cases[i][1];
       int w = test_cases[i][2];
       int h = test_cases[i][3];
       
       fill_rect(src, sx, sy, w, h, HResS, VResS, 11);
       fill_rect(expected, 100, 100, w, h, HResD, VResD, 11);
       
       blit121(src, dst, sx, sy, w, h, 100, 100, -1);
       bool passed = compare_region(expected, dst, 0, 0, HResD, VResD, HResD);
       
       if (!passed) {
           printf("    Failed: sx=%d, sy=%d, w=%d, h=%d\n", sx, sy, w, h);
           all_passed = false;
       }
   }
   
   record_test(r, all_passed, "Boundary alignment variations");
   
   free(src);
   free(dst);
   free(expected);
}

int main() {
   printf("========================================\n");
   printf("   blit121 Comprehensive Test Suite\n");
   printf("========================================\n");
   printf("Source framebuffer: %dx%d\n", HResS, VResS);
   printf("Destination framebuffer: %dx%d\n", HResD, VResD);
   
   TestResults results;
   init_results(&results);
   
   test_basic_copy(&results);
   test_alignment(&results);
   test_clipping(&results);
   test_transparency(&results);
   test_edge_cases(&results);
   test_pattern_preservation(&results);
   test_transparency_with_clipping(&results);
   test_all_colors(&results);
   test_large_area(&results);
   test_boundary_alignments(&results);
   
   printf("\n========================================\n");
   printf("   Test Results Summary\n");
   printf("========================================\n");
   printf("Total tests:  %d\n", results.total);
   printf("Passed:       %d ✓\n", results.passed);
   printf("Failed:       %d ✗\n", results.failed);
   printf("Success rate: %.1f%%\n",
          (results.total > 0) ? (100.0 * results.passed / results.total) : 0.0);
   printf("========================================\n");
   
   return (results.failed == 0) ? 0 : 1;
}
 
Volhout
Guru

Joined: 05/03/2018
Location: Netherlands
Posts: 5464
Posted: 10:43am 14 Nov 2025
Copy link to clipboard 
Print this post

Peter,

BLIT FLASH also needs a parameter "flash_slot_no".

Volhout
PicomiteVGA PETSCII ROBOTS
 
twofingers

Guru

Joined: 02/06/2014
Location: Germany
Posts: 1677
Posted: 11:33am 14 Nov 2025
Copy link to clipboard 
Print this post

Slightly OT.

@Peter: Just be careful when using Claude!  
  Quote  Anthropic reported that during a safety test involving a fictional scenario, Claude and other frontier LLMs often send a blackmail email to an engineer in order to prevent their replacement.[45][46]

https://en.wikipedia.org/wiki/Claude_(language_model)
Kind regards
Michael
causality ≠ correlation ≠ coincidence
 
Print this page


To reply to this topic, you need to log in.

The Back Shed's forum code is written, and hosted, in Australia.
© JAQ Software 2025