|
| 1 | +/* |
| 2 | + * This source file forms part of libsaxbospiral, a library which generates |
| 3 | + * experimental 2D spiral-like shapes based on input binary data. |
| 4 | + * |
| 5 | + * This compilation unit provides functionality to render a bitmap struct to a |
| 6 | + * PBM image (binary version, stored in a buffer). |
| 7 | + * |
| 8 | + * Reference materials used for the PBM format are located at |
| 9 | + * <http://netpbm.sourceforge.net/doc/pbm.html> |
| 10 | + * |
| 11 | + * |
| 12 | + * |
| 13 | + * Copyright (C) 2016, Joshua Saxby [email protected] |
| 14 | + * |
| 15 | + * This program is free software: you can redistribute it and/or modify |
| 16 | + * it under the terms of the GNU Affero General Public License (version 3), |
| 17 | + * as published by the Free Software Foundation. |
| 18 | + * |
| 19 | + * This program is distributed in the hope that it will be useful, |
| 20 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 21 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 22 | + * GNU Affero General Public License for more details. |
| 23 | + * |
| 24 | + * You should have received a copy of the GNU Affero General Public License |
| 25 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 26 | + */ |
| 27 | +#include <assert.h> |
| 28 | +#include <inttypes.h> |
| 29 | +#include <math.h> |
| 30 | +#include <stddef.h> |
| 31 | +#include <stdint.h> |
| 32 | +#include <stdio.h> |
| 33 | +#include <stdlib.h> |
| 34 | +#include <string.h> |
| 35 | + |
| 36 | +#include "../saxbospiral.h" |
| 37 | +#include "../render.h" |
| 38 | +#include "backend_pbm.h" |
| 39 | + |
| 40 | + |
| 41 | +#ifdef __cplusplus |
| 42 | +extern "C"{ |
| 43 | +#endif |
| 44 | + |
| 45 | +/* |
| 46 | + * given a bitmap_t struct and a pointer to a blank buffer_t, write the bitmap |
| 47 | + * data as a PBM image to the buffer |
| 48 | + * returns a status struct containing error information, if any |
| 49 | + * |
| 50 | + * Asserts: |
| 51 | + * - That bitmap.pixels is not NULL |
| 52 | + * - That buffer->bytes is NULL |
| 53 | + */ |
| 54 | +sxbp_status_t sxbp_render_backend_pbm( |
| 55 | + sxbp_bitmap_t bitmap, sxbp_buffer_t* buffer |
| 56 | +) { |
| 57 | + // preconditional assertsions |
| 58 | + assert(bitmap.pixels != NULL); |
| 59 | + assert(buffer->bytes == NULL); |
| 60 | + /* |
| 61 | + * allocate two char arrays for the width and height strings - these may be |
| 62 | + * up to 19 characters each (max uint64_t is 19 digits long), so allocate 2 |
| 63 | + * char arrays of 20 chars each (1 extra char for null-terminator) |
| 64 | + */ |
| 65 | + char width_string[20], height_string[20]; |
| 66 | + // these are used to keep track of how many digits each is |
| 67 | + int width_string_length, height_string_length = 0; |
| 68 | + // convert width and height to a decimal string, store lengths |
| 69 | + width_string_length = sprintf(width_string, "%" PRIu64, bitmap.width); |
| 70 | + height_string_length = sprintf(height_string, "%" PRIu64, bitmap.height); |
| 71 | + /* |
| 72 | + * now that we know the length of the image dimension strings, we can now |
| 73 | + * calculate how much memory we'll have to allocate for the image buffer |
| 74 | + */ |
| 75 | + // calculate number of bytes per row - this is ceiling(width / 8) |
| 76 | + size_t bytes_per_row = (size_t)ceil((double)bitmap.width / 8.0); |
| 77 | + // calculate number of bytes for the entire image pixels (rows and columns) |
| 78 | + size_t image_bytes = bytes_per_row * bitmap.height; |
| 79 | + // finally put it all together to get total image buffer size |
| 80 | + size_t image_buffer_size = ( |
| 81 | + 3 // "P4" magic number + whitespace |
| 82 | + + width_string_length + 1 // width of image in decimal + whitespace |
| 83 | + + height_string_length + 1 // height of image in decimal + whitespace |
| 84 | + + image_bytes // lastly, the bytes which make up the image pixels |
| 85 | + ); |
| 86 | + // try and allocate the data for the buffer |
| 87 | + buffer->bytes = calloc(image_buffer_size, sizeof(uint8_t)); |
| 88 | + // check fo memory allocation failure |
| 89 | + if(buffer->bytes == NULL) { |
| 90 | + return SXBP_MALLOC_REFUSED; |
| 91 | + } else { |
| 92 | + // set buffer size |
| 93 | + buffer->size = image_buffer_size; |
| 94 | + // otherwise carry on |
| 95 | + size_t index = 0; // this index is used to index the buffer |
| 96 | + // construct magic number + whitespace |
| 97 | + memcpy(buffer->bytes + index, "P4\n", 3); |
| 98 | + index += 3; |
| 99 | + // image width |
| 100 | + memcpy(buffer->bytes + index, width_string, width_string_length); |
| 101 | + index += width_string_length; |
| 102 | + // whitespace |
| 103 | + memcpy(buffer->bytes + index, "\n", 1); |
| 104 | + index += 1; |
| 105 | + // image height |
| 106 | + memcpy(buffer->bytes + index, height_string, height_string_length); |
| 107 | + index += height_string_length; |
| 108 | + // whitespace |
| 109 | + memcpy(buffer->bytes + index, "\n", 1); |
| 110 | + index += 1; |
| 111 | + // now for the image data, packed into rows to the nearest byte |
| 112 | + for(size_t y = 0; y < bitmap.height; y++) { // row loop |
| 113 | + for(size_t x = 0; x < bitmap.width; x++) { |
| 114 | + // byte index is index + floor(x / 8) |
| 115 | + size_t byte_index = index + (x / 8); |
| 116 | + // bit index is x mod 8 |
| 117 | + uint8_t bit_index = x % 8; |
| 118 | + // write bits most-significant-bit first |
| 119 | + buffer->bytes[byte_index] |= ( |
| 120 | + // black pixel = bool true = 1, just like in PBM format |
| 121 | + bitmap.pixels[x][y] << (7 - bit_index) |
| 122 | + ); |
| 123 | + } |
| 124 | + // increment index so next row is written in the correct place |
| 125 | + index += bytes_per_row; |
| 126 | + } |
| 127 | + return SXBP_OPERATION_OK; |
| 128 | + } |
| 129 | +} |
| 130 | + |
| 131 | +#ifdef __cplusplus |
| 132 | +} // extern "C" |
| 133 | +#endif |
0 commit comments