about summary refs log tree commit diff
path: root/converter/pbm/pbmtoascii.c
blob: 634bea67bd16325e9865459ac3ca3b0fac356a20 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
/* pbmtoascii.c - read a PBM image and produce ASCII graphics
**
** Copyright (C) 1988, 1992 by Jef Poskanzer.
**
** Permission to use, copy, modify, and distribute this software and its
** documentation for any purpose and without fee is hereby granted, provided
** that the above copyright notice appear in all copies and that both that
** copyright notice and this permission notice appear in supporting
** documentation.  This software is provided "as is" without express or
** implied warranty.
*/

#include <assert.h>
#include "mallocvar.h"
#include "pbm.h"

/*
  The algorithm is based on describing the 2 or 8 pixels in a cell with a
  single integer called a signature, which we use as an index into an array to
  get the character whose glyph best matches those pixels.  The encoding is as
  follows.  Make a string of bits, with each bit being one pixel of the cell,
  1 = black, 0 = white.  The order of the string is left to right across the
  top row, then the next row down, etc.  Considering that string to be a
  binary cipher, the integer it represents is the signature.

  Example: the 2x4 cell consists of these pixels: (* = black)

      * *
      *

        *
  The bit string to represent this is 11100001.  So the signature for this
  cell is the integer 0xE1 (225).

  You index the array carr2x4 with 0xE1, and get '?' as the character to
  represent that cell.  (We don't really try very hard to match the shape;
  it's mostly important to match the density).
*/

#define SQQ '\''
#define BSQ '\\'

/* Bit-map for 1x2 mode:
**     1
**     2
*/
static char const carr1x2[4] = {
/*   0    1    2    3   */
    ' ', '"', 'o', 'M' };

/* Bit-map for 2x4 mode (hex):
**     1  2
**     4  8
**     10 20
**     40 80
** The idea here is first to preserve geometry, then to show density.
*/
#define D08 'M'
#define D07 'H'
#define D06 '&'
#define D05 '$'
#define D04 '?'
static char const carr2x4[256] = {
/*0  1    2   3    4   5    6   7    8   9    A   B    C   D    E   F  */
' ',SQQ, '`','"', '-',SQQ, SQQ,SQQ, '-','`', '`','`', '-','^','^','"',/*00-0F*/
'.',':', ':',':', '|','|', '/',D04, '/','>', '/','>', '~','+','/','*',/*10-1F*/
'.',':', ':',':', BSQ,BSQ, '<','<', '|',BSQ, '|',D04, '~',BSQ,'+','*',/*20-2F*/
'-',':', ':',':', '~',D04, '<','<', '~','>', D04,'>', '=','b','d','#',/*30-3F*/
'.',':', ':',':', ':','!', '/',D04, ':',':', '/',D04, ':',D04,D04,'P',/*40-4F*/
',','i', '/',D04, '|','|', '|','T', '/',D04, '/','7', 'r','}','/','P',/*50-5F*/
',',':', ';',D04, '>',D04, 'S','S', '/',')', '|','7', '>',D05,D05,D06,/*60-6F*/
'v',D04, D04,D05, '+','}', D05,'F', '/',D05, '/',D06, 'p','D',D06,D07,/*70-7F*/
'.',':', ':',':', ':',BSQ, ':',D04, ':',BSQ, '!',D04, ':',D04,D04,D05,/*80-8F*/
BSQ,BSQ, ':',D04, BSQ,'|', '(',D05, '<','%', D04,'Z', '<',D05,D05,D06,/*90-9F*/
',',BSQ, 'i',D04, BSQ,BSQ, D04,BSQ, '|','|', '|','T', D04,BSQ,'4','9',/*A0-AF*/
'v',D04, D04,D05, BSQ,BSQ, D05,D06, '+',D05, '{',D06, 'q',D06,D06,D07,/*B0-BF*/
'_',':', ':',D04, ':',D04, D04,D05, ':',D04, D04,D05, ':',D05,D05,D06,/*C0-CF*/
BSQ,D04, D04,D05, D04,'L', D05,'[', '<','Z', '/','Z', 'c','k',D06,'R',/*D0-DF*/
',',D04, D04,D05, '>',BSQ, 'S','S', D04,D05, 'J',']', '>',D06,'1','9',/*E0-EF*/
'o','b', 'd',D06, 'b','b', D06,'6', 'd',D06, 'd',D07, '#',D07,D07,D08 /*F0-FF*/
};



static void
makeRowOfSignatures(FILE *         const ifP,
                    unsigned int   const cols,
                    unsigned int   const rows,
                    int            const format,
                    unsigned int   const cellWidth,
                    unsigned int   const cellHeight,
                    unsigned int   const row,
                    unsigned int * const sig,
                    unsigned int   const ccols) {
/*----------------------------------------------------------------------------
   Compute the signatures for every cell in a row.

   Read the pixels from *ifP, which is positioned to the first pixel row
   of the cell row, which is row number 'row'.  The image dimensions are
   'cols' x 'rows' pixels.

   Each cell is 'cellWidth' x 'cellHeight'.

   Return the signatures as sig[], which is 'ccols' wide because that's how
   many cells you get from 'cols' pixels divided into cells 'cellWidth' pixels
   wide.
-----------------------------------------------------------------------------*/
    unsigned int b;
    unsigned int subrow;  /* row within cell */
    bit * bitrow;        /* malloc'ed array */

    bitrow = pbm_allocrow(cols);
    {
        unsigned int col;
        for (col = 0; col < ccols; ++col)
            sig[col] = 0;
    }

    b = 0x1;  /* initial value */
    for (subrow = 0; subrow < cellHeight; ++subrow) {
        if (row + subrow < rows) {
            unsigned int subcol;  /* col within cell */
            pbm_readpbmrow(ifP, bitrow, cols, format);
            for (subcol = 0; subcol < cellWidth; ++subcol) {
                unsigned int col, ccol;
                for (col = subcol, ccol = 0;
                     col < cols;
                     col += cellWidth, ++ccol) {
                    if (bitrow[col] == PBM_BLACK)
                        sig[ccol] |= b;
                }
                b <<= 1;
            }
        }
    }
    pbm_freerow(bitrow);
}



static void
findRightMargin(const unsigned int * const sig,
                unsigned int         const ccols,
                const char *         const carr,
                unsigned int *       const endColP) {
/*----------------------------------------------------------------------------
   Find the first cell of the right margin, i.e. a contiguous set of
   all-white cells at the right end of the row.
-----------------------------------------------------------------------------*/
    unsigned int endcol;

    for (endcol = ccols; endcol > 0; --endcol) {
        if (carr[sig[endcol-1]] != ' ')
            break;
    }
    *endColP = endcol;
}



static void
assembleCellRow(const unsigned int * const sig,
                unsigned int         const ccols,
                const char *         const carr,
                char *               const line) {
/*----------------------------------------------------------------------------
   Return as line[] the line of ASCII codes for the characters of one
   row of cells, ready for printing.
-----------------------------------------------------------------------------*/
    unsigned int col;

    for (col = 0; col < ccols; ++col)
        line[col] = carr[sig[col]];

    line[ccols] = '\0';
}



static void
pbmtoascii(FILE *       const ifP,
           unsigned int const cellWidth,
           unsigned int const cellHeight,
           const char * const carr) {

    int format;
    int cols, rows;
        /* Dimensions of the input in pixels */
    unsigned int ccols;
        /* Width of the output in characters */
    char * line;         /* malloc'ed array */
    unsigned int row;
    unsigned int * signatureRow;  /* malloc'ed array */
        /* This is the cell signatures of a row of cells.
           signatureRow[0] is the signature for the first (leftmost) cell
           in the row, signatureRow[1] is the signature for the next one,
           etc.  A signature is an encoding of the pixels of a cell as
           an integer, as described above.
        */
    assert(cellWidth * cellHeight <= sizeof(signatureRow[0])*8);

    pbm_readpbminit(ifP, &cols, &rows, &format);

    ccols = (cols + cellWidth - 1) / cellWidth;

    MALLOCARRAY(signatureRow, ccols);
    if (signatureRow == NULL)
        pm_error("No memory for %u columns", ccols);

    MALLOCARRAY_NOFAIL(line, ccols+1);
    if (line == NULL)
        pm_error("No memory for %u columns", ccols);

    for (row = 0; row < rows; row += cellHeight) {
        unsigned int endCol;

        makeRowOfSignatures(ifP, cols, rows, format, cellWidth, cellHeight,
                            row, signatureRow, ccols);

        findRightMargin(signatureRow, ccols, carr, &endCol);

        assembleCellRow(signatureRow, endCol, carr, line);

        puts(line);
    }
    free(signatureRow);
    free(line);
}



int
main(int argc, const char ** argv) {

    FILE * ifP;
    int argn, gridx, gridy;
    const char * carr;
    const char* usage = "[-1x2|-2x4] [pbmfile]";

    pm_proginit(&argc, argv);

    /* Set up default parameters. */
    argn = 1;
    gridx = 1;
    gridy = 2;
    carr = carr1x2;

    /* Check for flags. */
    while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
    {
        if ( pm_keymatch( argv[argn], "-1x2", 2 ) )
        {
            gridx = 1;
            gridy = 2;
            carr = carr1x2;
        }
        else if ( pm_keymatch( argv[argn], "-2x4", 2 ) )
        {
            gridx = 2;
            gridy = 4;
            carr = carr2x4;
        }
        else
            pm_usage( usage );
        ++argn;
    }

    if ( argn < argc )
    {
        ifP = pm_openr( argv[argn] );
        ++argn;
    }
    else
        ifP = stdin;

    if ( argn != argc )
        pm_usage( usage );

    pbmtoascii(ifP, gridx, gridy, carr);

    pm_close(ifP);

    return 0;
}