/* qbism.c * * Copyright (C) 2005-2006 by Andy Goth . * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., 59 Temple * Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include "qbism.h" /* Write a little-endian two-byte integer. */ #define put_le_w(chan, val) do {\ fputc(((val) >> 0) & 0xff, (chan));\ fputc(((val) >> 8) & 0xff, (chan));\ } while (0) /* Write a little-endian four-byte integer. */ #define put_le_l(chan, val) do {\ fputc(((val) >> 0) & 0xff, (chan));\ fputc(((val) >> 8) & 0xff, (chan));\ fputc(((val) >> 16) & 0xff, (chan));\ fputc(((val) >> 24) & 0xff, (chan));\ } while (0) static pixel_t qbism_render_subpixel(image_t* img, float x, float y); static pixel_t qbism_render_pixel(image_t* img, int x, int y); /* Render and return an individual subpixel. */ static inline pixel_t qbism_render_subpixel(image_t* img, float x, float y) { int i, step; /* Channel and step counters. */ algo_t* algo = img->algo; /* Algorithm to use. */ pixel_t reg[algo->num_regs]; /* Register array. */ pixel_t* sr, *cr, *dr; /* Source, control, destination register. */ /* Initialize registers for this sample point. */ for (i = 0; i < algo->num_regs; ++i) { reg[i].r = x; reg[i].g = y; reg[i].b = i / (float)algo->num_regs; } /* Apply transformations. */ for (step = 0; step < algo->seq_len; ++step) { /* Get handy pointers to the registers in question. */ sr = ®[algo->seq[step].source]; cr = ®[algo->seq[step].control]; dr = ®[algo->seq[step].dest]; /* Update the registers for this sample point. */ switch (algo->seq[step].opcode) { case XOP_PROJECTION: { /* dest = sawtooth(source * (source . control)) */ float dot_product = 0; for (i = 0; i < 3; ++i) { dot_product += sr->c[i] * cr->c[i]; } for (i = 0; i < 3; ++i) { dr->c[i] = dot_product * sr->c[i]; while (dr->c[i] >= 1) { dr->c[i] -= 1; } } break; } case XOP_SHIFT: /* dest = sawtooth(source + control) */ for (i = 0; i < 3; ++i) { dr->c[i] = sr->c[i] + cr->c[i]; if (dr->c[i] >= 1) { dr->c[i] -= 1; } } break; case XOP_SHIFTBACK: /* dest = sawtooth(source - control) */ for (i = 0; i < 3; ++i) { dr->c[i] = sr->c[i] - cr->c[i]; if (dr->c[i] < 0) { dr->c[i] += 1; } } break; case XOP_ROTATE: /* dest = rotate(source, g->r, b->g, r->b) */ dr->r = sr->g; dr->g = sr->b; dr->b = sr->r; break; case XOP_ROTATEBACK: /* dest = rotate(source, b->r, r->g, g->b) */ dr->r = sr->b; dr->g = sr->r; dr->b = sr->g; break; case XOP_MULTIPLY: /* dest = source * control */ for (i = 0; i < 3; ++i) { dr->c[i] = sr->c[i] * cr->c[i]; } break; case XOP_SINE: /* dest = sin(20 * source * control) */ for (i = 0; i < 3; ++i) { dr->c[i] = (sin(20 * sr->c[i] * cr->c[i]) + 1) / 2; if (dr->c[i] == 1) { dr->c[i] = 0.999999; } } break; case XOP_CONDITIONAL: { /* dest = channel_sum(source) > 0.5 ? source : control */ float sum = 0.0; for (i = 0; i < 3; ++i) { sum += cr->c[i]; } if (sum > 0.5) { *dr = *sr; } else { *dr = *cr; } break; } case XOP_COMPLEMENT: /* dest = sawtooth(-source) */ for (i = 0; i < 3; ++i) { dr->c[i] = 1 - sr->c[i]; if (dr->c[i] == 1) { dr->c[i] = 0.999999; } } break; } } /* Return the result at this sample point. */ return reg[0]; } /* Render an individual pixel as the average of one or more subpixels. */ static inline pixel_t qbism_render_pixel(image_t* img, int x, int y) { int i; /* Channel counter. */ int xv, yv; /* Subpixel coordinates (0 to 1). */ float xf, yf; /* Subpixel coordinates (0 to os). */ int w = img->width; /* Number of columns of real pixels. */ int h = img->height; /* Number of rows of real pixels. */ int os = img->oversample; /* Number of rows and columns of subpixels. */ pixel_t pixel = /* Cumulative sum of subpixel values. */ {{0.0, 0.0, 0.0}}; /* Start with black. */ /* For each subpixel in this physical pixel... */ for (yv = 0, yf = y / (float)h; yv < os; ++yv, yf += 1.0 / h / os) { for (xv = 0, xf = x / (float)w; xv < os; ++xv, xf += 1.0 / w / os) { /* Calculate the subpixel. */ pixel_t subpixel = qbism_render_subpixel(img, xf, yf); /* Add the subpixel to the pixel. */ for (i = 0; i < 3; ++i) { pixel.c[i] += subpixel.c[i]; } } } /* Divide the pixel by the number of subpixels. */ for (i = 0; i < 3; ++i) { pixel.c[i] /= os * os; } /* Return this pixel. */ return pixel; } /* Render an image to an output file. */ void qbism_render_image(image_t* img) { int i, x, y; int y0 = 0; /* First row to render. */ int y1 = img->height; /* One past the final row to render. */ int yd = 1; /* Row increment. */ int row_filler = 0; /* Extra bytes after each row. */ /* Write image header .*/ switch (img->format) { case IF_MIFF8: case IF_MIFF16: /* Output MIFF header. */ fprintf(img->target, "id=ImageMagick\n"); fprintf(img->target, "columns=%d\n", img->width); fprintf(img->target, "rows=%d\n", img->height); fprintf(img->target, "depth=%d\n", img->format == IF_MIFF8 ? 8 : 16); fprintf(img->target, "\f\n:\x01a"); break; case IF_BMP: /* Output BMP header. */ put_le_w(img->target, 0x4d42); put_le_l(img->target, 54 + (img->width * 3 + row_filler) * img->height); put_le_l(img->target, 0); put_le_l(img->target, 54); put_le_l(img->target, 40); put_le_l(img->target, img->width); put_le_l(img->target, img->height); put_le_w(img->target, 1); put_le_w(img->target, 24); put_le_l(img->target, 0); put_le_l(img->target, (img->width * 3 + row_filler) * img->height); put_le_l(img->target, 0x812); put_le_l(img->target, 0x812); put_le_l(img->target, 0); put_le_l(img->target, 0); /* BMPs are stored upside-down and have filler between scanlines. */ y0 = img->height - 1; y1 = -1; yd = -1; row_filler = 3 - ((img->width * 3 - 1) & 3); break; } /* For each physical pixel... */ for (y = y0; y != y1; y += yd) { for (x = 0; x < img->width; ++x) { /* Render the pixel. */ pixel_t pixel = qbism_render_pixel(img, x, y); /* Output the pixel. */ switch (img->format) { case IF_MIFF8: /* 8-bit MIFF uses RGB order. */ for (i = 0; i < 3; ++i) { fputc(pixel.c[i] * 255 + 0.5, img->target); } break; case IF_MIFF16: /* 16-bit MIFF uses RRGGBB order. */ for (i = 0; i < 3; ++i) { int val = pixel.c[i] * 65535 + 0.5; fputc(val >> 8, img->target); fputc(val >> 0, img->target); } break; case IF_BMP: /* BMP stores pixels in BGR order. */ for (i = 2; i >= 0; --i) { fputc(pixel.c[i] * 255 + 0.5, img->target); } break; } } /* Finish this scanline. */ for (i = 0; i < row_filler; ++i) { fputc(0, img->target); } } } /* vim: set ts=4 sts=4 sw=4 tw=80 et: */