1 /*
2  * xvzx.c - load routine for Spectrum screen$
3  *
4  * John Elliott, 7 August 1998
5  *
6  * LoadZX(fname, pinfo)  -  load file
7  * WriteZX(fp,pic,ptype,w,h,r,g,b,numcols,style,cmt,comment) - convert to
8  *                          256x192 SCREEN$ and save.
9  */
10 
11 #include "copyright.h"
12 
13 #include "xv.h"
14 
15 
16 
17 /*
18  * comments on error handling:
19  * a file with a bad header checksum is a warning error.
20  *
21  * not being able to malloc is a Fatal Error.  The program is aborted.
22  */
23 
24 
25 #define TRUNCSTR "File appears to be truncated."
26 
27 static int zxError PARM((const char *, const char *));
28 
29 static const char *bname;
30 
31 /*******************************************/
LoadZX(fname,pinfo)32 int LoadZX(fname, pinfo)
33      char    *fname;
34      PICINFO *pinfo;
35 /*******************************************/
36 {
37   /* returns '1' on success */
38 
39   FILE  *fp;
40   unsigned int    c, c1;
41   int   x,y, trunc;
42   byte  *zxfile;
43 
44   bname = BaseName(fname);
45 
46   pinfo->pic     = (byte *) NULL;
47   pinfo->comment = (char *) NULL;
48 
49   /* Allocate memory for a 256x192x8bit image */
50 
51   pinfo->pic     = (byte *)malloc(256*192);
52   if (!pinfo->pic) FatalError("malloc failure in xvzx.c LoadZX");
53 
54   /* Allocate 1B80h bytes and slurp the whole file into memory */
55 
56   zxfile         = (byte *)malloc(7040);
57   if (!zxfile)     FatalError("malloc failure in xvzx.c LoadZX");
58 
59   /* open the file */
60   fp = xv_fopen(fname,"r");
61   if (!fp) return (zxError(bname, "can't open file"));
62 
63   /* Load it in en bloc */
64   memset(zxfile, 0, 7040);
65   if (fread(zxfile, 1, 7040, fp) < 7040) trunc = 1;
66 
67   /* Transform to 8-bit */
68 
69   for (y = 0; y < 192; y++) for (x = 0; x < 256; x++)
70   {
71      /* Spectrum screen layout: three 2k segments at y=0, y=64, y=128 */
72      /* In each segment: Scan lines 0,8,16,...,56,1,9,...,57 etc.  Each
73         scanline is 32 bytes, so line 1 is 256 bytes after line 0
74 
75         So address of line start is ((y>>6) * 2048) + ((y & 7) * 256)
76         + ((y & 0x38) * 4)
77 
78        The colour byte for a cell is at screen + 6k + (y >> 3)*32 + (x>>3)
79 
80        */
81 
82      int offset;
83      byte *dst  = pinfo->pic + 256*y + x;
84      byte attr, pt, mask;
85 
86      offset  = (y >> 6)   * 2048;
87      offset += (y & 7)    * 256;
88      offset += (y & 0x38) * 4;
89      offset += (x >> 3);
90 
91      pt = zxfile[offset + 128];	/* Ink/paper map */
92 
93      offset = 0x1880;
94      offset += (y >> 3) * 32;
95      offset += (x >> 3);
96 
97      attr = zxfile[offset]; 	/* Colours for cell */
98 
99      mask = 0x80;
100 
101      if (x & 7) mask >>= (x & 7);
102 
103      if (pt & mask)  *dst = attr & 7;        /* Ink */
104      else            *dst = (attr >> 3) & 7; /* Paper */
105 
106      if (attr & 0x40) *dst |= 8; /* High intensity */
107   }
108 
109   /* Picture bytes converted; now build the colour maps */
110 
111   pinfo->normw = pinfo->w = 256;
112   pinfo->normh = pinfo->h = 192;
113   pinfo->type = PIC8;
114 
115   for (c = 0; c < 16; c++)
116   {
117     if (c < 8) c1 = 192; else c1 = 255;	/* low-intensity colours use 192 */
118 					/* high-intensity colours use 255 */
119     pinfo->b[c] = (c & 1 ? c1 : 0);
120     pinfo->r[c] = (c & 2 ? c1 : 0);
121     pinfo->g[c] = (c & 4 ? c1 : 0);
122   }
123 
124   pinfo->colType = F_FULLCOLOR;
125   pinfo->frmType = F_ZX;		/* Save as SCREEN$ */
126   sprintf(pinfo->fullInfo, "Spectrum SCREEN$, load address %04x",
127                                  zxfile[16]+256*zxfile[17]);
128   strcpy(pinfo->shrtInfo, "Spectrum SCREEN$.");
129 
130   /* Almost as an afterthought, check that the +3DOS header is valid.
131 
132      If it isn't, then odds are that the file isn't a graphic. But it
133      had the right magic number, so it might be. Let them see it anyway.
134 
135      The check is: Byte 127 of the header should be the 8-bit sum of bytes
136                    0-126 of the header. The header should also have the
137                    +3DOS magic number, but we know it does or we wouldn't
138                    have got this far.
139   */
140 
141   c1 = 0;
142   for (c1 = c = 0; c < 127; c++) c1 = ((c1 + zxfile[c]) & 0xFF);
143   if (c1 != zxfile[127]) zxError(bname, "Bad header checksum.");
144 
145   fclose(fp);
146   free(zxfile);
147   return 1;
148 }
149 
150 
151 
152 
153 
154 /*******************************************/
zxError(fname,st)155 static int zxError(fname, st)
156      const char *fname, *st;
157 {
158   SetISTR(ISTR_WARNING,"%s:  %s", fname, st);
159   return 0;
160 }
161 
162 
163 /* Spectrum screen file header. The first 18 bytes are used in the magic
164    number test */
165 
166 byte ZXheader[128] =
167 {
168     'P', 'L', 'U', 'S', '3', 'D', 'O', 'S', 26,	 /* Spectrum +3DOS file */
169       1,   0, 					 /* Header type 1.0 */
170     128,  27,  0,   0,				 /* 7040 bytes */
171       3,	                                 /* Binary format */
172       0, 27,   					 /* 6912 data bytes */
173       0, 64					 /* load address 0x4000 */
174 };
175 
176 
177 
178 /* Get the Spectrum colour/bright byte (0-15) from a pixel */
179 
PointZX(pic,w,h,rmap,gmap,bmap,x,y)180 static int PointZX(pic, w, h, rmap, gmap, bmap, x, y)
181 	byte *pic;
182 	int w,h;
183 	byte *rmap, *gmap, *bmap;
184 	int x,y;
185 {
186 	int index, r, g, b, zxc;
187 
188 	/* If the picture is smaller than the screen, pad out the edges
189            with "bright black" - a colour not otherwise returned */
190 
191 	if (x >= w || y >= h) return 8;
192 
193 	/* Get colour index */
194 
195 	index = pic[y*w + x];
196 
197 	/* Convert to rgb */
198 
199 	r = rmap[index];
200 	g = gmap[index];
201 	b = bmap[index];
202 	zxc = 0;
203 
204 	/* Work out Spectrum colour by a simplistic "nearest colour" method */
205 
206 	if (b >= 160) zxc |= 1;	/* Blue */
207 	if (r >= 160) zxc |= 2;	/* Red */
208 	if (g >= 160) zxc |= 4;	/* Green */
209 	if (r > 208 || g >= 208 || b >= 208) zxc |= 8; /* High intensity */
210 
211 	return zxc;
212 }
213 
214 
215 /* Work out what colours should be used in a cell */
216 
CellZX(pic,w,h,rmap,gmap,bmap,cx,cy,zxfile)217 static void CellZX(pic, w, h, rmap, gmap, bmap, cx, cy, zxfile)
218         byte *pic;
219         int w,h;
220         byte *rmap, *gmap, *bmap;
221         int cx,cy;
222 	byte *zxfile;
223 {
224 	byte counts[16];	/* Count of no. of colours */
225 	int offset, ink, paper, n, m, x, y, x0, y0, di, dp;
226 
227 	x0 = cx * 8;		/* Convert from cell to pixel coords */
228 	y0 = cy * 8;
229 
230 	for (n = 0; n < 16; n++) counts[n] = 0;	 /* Reset all counts */
231 
232 	/* Count no. of pixels of various colours */
233 
234 	for (y = y0; y < y0+8; y++) for (x = x0; x < x0+8; x++)
235 	{
236 		m = PointZX(pic, w, h, rmap, gmap, bmap, x, y);
237 
238 		counts[m]++;
239 	}
240 	counts[8] = 0;	/* Discard Bright Black (pixels not in the picture area)
241                          */
242 
243 	/* Assign the most popular colour as ink */
244 	for (n = m = ink = 0; n < 16; n++) if (counts[n] > m)
245 	{
246 		ink = n;
247 		m   = counts[n];
248 	}
249 	counts[ink] = 0;
250 
251 	/* Assign the next most popular colour as paper */
252 	for (n = m = paper = 0; n < 16; n++) if (counts[n] > m)
253 	{
254 		paper = n;
255 		m     = counts[n];
256 	}
257 	/* We have ink and paper. Set cell's attributes */
258 
259 	offset = cy*32 + cx + 0x1880;
260 
261 	/* Set the high-intensity bit if ink is high-intensity */
262 	if (ink & 8) zxfile[offset] = 0x40; else zxfile[offset] = 0;
263 	zxfile[offset] |= ((paper & 7) << 3);
264 	zxfile[offset] |=  (ink & 7);
265 
266 	/* Plot the points */
267 	for (y = y0; y < y0+8; y++)
268 	{
269 	     byte mask = 0x80;
270 
271 	     offset  = (y >> 6)   * 2048;
272 	     offset += (y & 7)    * 256;
273 	     offset += (y & 0x38) * 4;
274 	     offset += (x0 >> 3);
275 
276 	     for (x = x0; x < x0+8; x++)
277 	     {
278 		/* Work out whether the point should be plotted as ink or
279                    paper */
280 		m = PointZX(pic, w, h, rmap, gmap, bmap, x, y);
281 
282 		di = (ink & 7)   - (m & 7);	/* "Difference" from ink */
283 		dp = (paper & 7) - (m & 7);	/* "Difference" from paper */
284 
285 		if (di < 0) di = -di;
286 		if (dp < 0) dp = -dp;
287 
288 		if (di < dp)	/* Point is more like ink */
289 			zxfile[offset+128] |= mask;
290 
291 		mask = (mask >> 1);
292              }
293 	}
294 
295 }
296 
297 
298 
299 /*******************************************/
WriteZX(fp,pic,ptype,w,h,rmap,gmap,bmap,numcols,colorstyle,comment)300 int WriteZX(fp,pic,ptype,w,h,rmap,gmap,bmap,numcols,colorstyle,comment)
301      FILE *fp;
302      byte *pic;
303      int   ptype, w,h;
304      byte *rmap, *gmap, *bmap;
305      int   numcols, colorstyle;
306      char *comment;
307 {
308 	int rv, x, y;
309 	byte *zxfile;
310 	byte *pic8;
311 	byte  rtemp[256],gtemp[256],btemp[256];
312 
313 	/* To simplify matters, reduce 24-bit to 8-bit. Since the Spectrum
314            screen is 3.5-bit anyway, it doesn't make much difference */
315 
316 	if (ptype == PIC24)
317 	{
318 		pic8 = Conv24to8(pic, w, h, 256, rtemp,gtemp,btemp);
319 		if (!pic8) FatalError("Unable to malloc in WriteZX()");
320 		rmap = rtemp;  gmap = gtemp;  bmap = btemp;  numcols=256;
321   	}
322 	else pic8 = pic;
323 
324 	ZXheader[127] = 0x71;	/* The correct checksum. */
325 
326 	/* Create a memory image of the SCREEN$ */
327 
328 	zxfile         = (byte *)malloc(7040);
329 	if (!zxfile)     FatalError("malloc failure in xvzx.c WriteZX");
330 
331 	memset(zxfile, 0, 7040);	/* Reset all points to black */
332 	memcpy(zxfile, ZXheader, 128);	/* Create +3DOS header */
333 
334         /* Convert the image, character cell by character cell */
335         for (y = 0; y < 24; y++) for (x = 0; x < 32; x++)
336         {
337                 CellZX(pic8, w, h, rmap, gmap, bmap, x, y, zxfile);
338         }
339 	rv = 0;
340 	if (fwrite(zxfile, 1, 7040, fp) < 7040) rv = -1;
341 
342 	if (ptype == PIC24) free(pic8);
343 	free(zxfile);
344 
345 	if (ferror(fp)) rv = -1;
346 
347 	return rv;
348 }
349 
350