Leptonica  1.77.0
Image processing and image analysis suite
gifio.c
Go to the documentation of this file.
1 /*====================================================================*
2  - Copyright (C) 2001 Leptonica. All rights reserved.
3  -
4  - Redistribution and use in source and binary forms, with or without
5  - modification, are permitted provided that the following conditions
6  - are met:
7  - 1. Redistributions of source code must retain the above copyright
8  - notice, this list of conditions and the following disclaimer.
9  - 2. Redistributions in binary form must reproduce the above
10  - copyright notice, this list of conditions and the following
11  - disclaimer in the documentation and/or other materials
12  - provided with the distribution.
13  -
14  - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15  - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16  - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17  - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
18  - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
23  - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24  - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  *====================================================================*/
26 
69 #ifdef HAVE_CONFIG_H
70 #include "config_auto.h"
71 #endif /* HAVE_CONFIG_H */
72 
73 #include <string.h>
74 #include "allheaders.h"
75 
76 /* --------------------------------------------------------------------*/
77 #if HAVE_LIBGIF || HAVE_LIBUNGIF /* defined in environ.h */
78 /* --------------------------------------------------------------------*/
79 
80 #include "gif_lib.h"
81 
82  /* Interface that enables low-level GIF support for reading from memory */
83 static PIX * gifToPix(GifFileType *gif);
84  /* Interface that enables low-level GIF support for writing to memory */
85 static l_int32 pixToGif(PIX *pix, GifFileType *gif);
86 
88 typedef struct GifReadBuffer
89 {
90  size_t size;
91  size_t pos;
92  const l_uint8 *cdata;
93 } GifReadBuffer;
94 
96 static l_int32 gifReadFunc(GifFileType *gif, GifByteType *dest,
97  l_int32 bytesToRead);
99 static l_int32 gifWriteFunc(GifFileType *gif, const GifByteType *src,
100  l_int32 bytesToWrite);
101 
102 
103 /*---------------------------------------------------------------------*
104  * Reading gif *
105  *---------------------------------------------------------------------*/
112 PIX *
113 pixReadStreamGif(FILE *fp)
114 {
115 l_uint8 *filedata;
116 size_t filesize;
117 PIX *pix;
118 
119  PROCNAME("pixReadStreamGif");
120 
121  if (!fp)
122  return (PIX *)ERROR_PTR("fp not defined", procName, NULL);
123 
124  /* Read data into memory from file */
125  rewind(fp);
126  if ((filedata = l_binaryReadStream(fp, &filesize)) == NULL)
127  return (PIX *)ERROR_PTR("filedata not read", procName, NULL);
128 
129  /* Uncompress from memory */
130  pix = pixReadMemGif(filedata, filesize);
131  LEPT_FREE(filedata);
132  if (!pix)
133  L_ERROR("failed to read gif from file data\n", procName);
134  return pix;
135 }
136 
137 
155 PIX *
156 pixReadMemGif(const l_uint8 *cdata,
157  size_t size)
158 {
159 GifFileType *gif;
160 GifReadBuffer buffer;
161 
162  PROCNAME("pixReadMemGif");
163 
164  /* 5.1+ and not 5.1.2 */
165 #if (GIFLIB_MAJOR < 5 || (GIFLIB_MAJOR == 5 && GIFLIB_MINOR == 0))
166  L_ERROR("Require giflib-5.1 or later\n", procName);
167  return NULL;
168 #endif /* < 5.1 */
169 #if GIFLIB_MAJOR == 5 && GIFLIB_MINOR == 1 && GIFLIB_RELEASE == 2 /* 5.1.2 */
170  L_ERROR("Can't use giflib-5.1.2; suggest 5.1.3 or later\n", procName);
171  return NULL;
172 #endif /* 5.1.2 */
173 
174  if (!cdata)
175  return (PIX *)ERROR_PTR("cdata not defined", procName, NULL);
176 
177  buffer.cdata = cdata;
178  buffer.size = size;
179  buffer.pos = 0;
180  if ((gif = DGifOpen((void*)&buffer, gifReadFunc, NULL)) == NULL)
181  return (PIX *)ERROR_PTR("could not open gif stream from memory",
182  procName, NULL);
183 
184  return gifToPix(gif);
185 }
186 
187 
188 static l_int32
189 gifReadFunc(GifFileType *gif,
190  GifByteType *dest,
191  l_int32 bytesToRead)
192 {
193 GifReadBuffer *buffer;
194 l_int32 bytesRead;
195 
196  PROCNAME("gifReadFunc");
197 
198  if ((buffer = (GifReadBuffer*)gif->UserData) == NULL)
199  return ERROR_INT("UserData not set", procName, -1);
200 
201  if(buffer->pos >= buffer->size || bytesToRead > buffer->size)
202  return -1;
203 
204  bytesRead = (buffer->pos < buffer->size - bytesToRead)
205  ? bytesToRead : buffer->size - buffer->pos;
206  memcpy(dest, buffer->cdata + buffer->pos, bytesRead);
207  buffer->pos += bytesRead;
208  return bytesRead;
209 }
210 
211 
225 static PIX *
226 gifToPix(GifFileType *gif)
227 {
228 l_int32 wpl, i, j, w, h, d, cindex, ncolors;
229 l_int32 rval, gval, bval;
230 l_uint32 *data, *line;
231 PIX *pixd;
232 PIXCMAP *cmap;
233 ColorMapObject *gif_cmap;
234 SavedImage si;
235 int giferr;
236 
237  PROCNAME("gifToPix");
238 
239  /* Read all the data, but use only the first image found */
240  if (DGifSlurp(gif) != GIF_OK) {
241  DGifCloseFile(gif, &giferr);
242  return (PIX *)ERROR_PTR("failed to read GIF data", procName, NULL);
243  }
244 
245  if (gif->SavedImages == NULL) {
246  DGifCloseFile(gif, &giferr);
247  return (PIX *)ERROR_PTR("no images found in GIF", procName, NULL);
248  }
249 
250  si = gif->SavedImages[0];
251  w = si.ImageDesc.Width;
252  h = si.ImageDesc.Height;
253  if (w <= 0 || h <= 0) {
254  DGifCloseFile(gif, &giferr);
255  return (PIX *)ERROR_PTR("invalid image dimensions", procName, NULL);
256  }
257 
258  if (si.RasterBits == NULL) {
259  DGifCloseFile(gif, &giferr);
260  return (PIX *)ERROR_PTR("no raster data in GIF", procName, NULL);
261  }
262 
263  if (si.ImageDesc.ColorMap) {
264  /* private cmap for this image */
265  gif_cmap = si.ImageDesc.ColorMap;
266  } else if (gif->SColorMap) {
267  /* global cmap for whole picture */
268  gif_cmap = gif->SColorMap;
269  } else {
270  /* don't know where to take cmap from */
271  DGifCloseFile(gif, &giferr);
272  return (PIX *)ERROR_PTR("color map is missing", procName, NULL);
273  }
274 
275  ncolors = gif_cmap->ColorCount;
276  if (ncolors <= 2)
277  d = 1;
278  else if (ncolors <= 4)
279  d = 2;
280  else if (ncolors <= 16)
281  d = 4;
282  else
283  d = 8;
284  if ((cmap = pixcmapCreate(d)) == NULL) {
285  DGifCloseFile(gif, &giferr);
286  return (PIX *)ERROR_PTR("cmap creation failed", procName, NULL);
287  }
288 
289  for (cindex = 0; cindex < ncolors; cindex++) {
290  rval = gif_cmap->Colors[cindex].Red;
291  gval = gif_cmap->Colors[cindex].Green;
292  bval = gif_cmap->Colors[cindex].Blue;
293  pixcmapAddColor(cmap, rval, gval, bval);
294  }
295 
296  if ((pixd = pixCreate(w, h, d)) == NULL) {
297  DGifCloseFile(gif, &giferr);
298  pixcmapDestroy(&cmap);
299  return (PIX *)ERROR_PTR("failed to allocate pixd", procName, NULL);
300  }
301  pixSetInputFormat(pixd, IFF_GIF);
302  pixSetColormap(pixd, cmap);
303 
304  wpl = pixGetWpl(pixd);
305  data = pixGetData(pixd);
306  for (i = 0; i < h; i++) {
307  line = data + i * wpl;
308  if (d == 1) {
309  for (j = 0; j < w; j++) {
310  if (si.RasterBits[i * w + j])
311  SET_DATA_BIT(line, j);
312  }
313  } else if (d == 2) {
314  for (j = 0; j < w; j++)
315  SET_DATA_DIBIT(line, j, si.RasterBits[i * w + j]);
316  } else if (d == 4) {
317  for (j = 0; j < w; j++)
318  SET_DATA_QBIT(line, j, si.RasterBits[i * w + j]);
319  } else { /* d == 8 */
320  for (j = 0; j < w; j++)
321  SET_DATA_BYTE(line, j, si.RasterBits[i * w + j]);
322  }
323  }
324 
325  /* Versions before 5.0 required un-interlacing to restore
326  * the raster lines to normal order if the image
327  * had been interlaced (for viewing in a browser):
328  if (gif->Image.Interlace) {
329  PIX *pixdi = pixUninterlaceGIF(pixd);
330  pixTransferAllData(pixd, &pixdi, 0, 0);
331  }
332  * This is no longer required. */
333 
334  DGifCloseFile(gif, &giferr);
335  return pixd;
336 }
337 
338 
339 /*---------------------------------------------------------------------*
340  * Writing gif *
341  *---------------------------------------------------------------------*/
356 l_ok
357 pixWriteStreamGif(FILE *fp,
358  PIX *pix)
359 {
360 l_uint8 *filedata;
361 size_t filebytes, nbytes;
362 
363  PROCNAME("pixWriteStreamGif");
364 
365  if (!fp)
366  return ERROR_INT("stream not open", procName, 1);
367  if (!pix)
368  return ERROR_INT("pix not defined", procName, 1);
369 
370  pixSetPadBits(pix, 0);
371  if (pixWriteMemGif(&filedata, &filebytes, pix) != 0) {
372  LEPT_FREE(filedata);
373  return ERROR_INT("failure to gif encode pix", procName, 1);
374  }
375 
376  rewind(fp);
377  nbytes = fwrite(filedata, 1, filebytes, fp);
378  LEPT_FREE(filedata);
379  if (nbytes != filebytes)
380  return ERROR_INT("write error", procName, 1);
381  return 0;
382 }
383 
384 
398 l_ok
399 pixWriteMemGif(l_uint8 **pdata,
400  size_t *psize,
401  PIX *pix)
402 {
403 int giferr;
404 l_int32 result;
405 GifFileType *gif;
406 L_BBUFFER *buffer;
407 
408  PROCNAME("pixWriteMemGif");
409 
410  /* 5.1+ and not 5.1.2 */
411 #if (GIFLIB_MAJOR < 5 || (GIFLIB_MAJOR == 5 && GIFLIB_MINOR == 0))
412  L_ERROR("Require giflib-5.1 or later\n", procName);
413  return 1;
414 #endif /* < 5.1 */
415 #if GIFLIB_MAJOR == 5 && GIFLIB_MINOR == 1 && GIFLIB_RELEASE == 2 /* 5.1.2 */
416  L_ERROR("Can't use giflib-5.1.2; suggest 5.1.3 or later\n", procName);
417  return 1;
418 #endif /* 5.1.2 */
419 
420  if (!pdata)
421  return ERROR_INT("&data not defined", procName, 1 );
422  *pdata = NULL;
423  if (!psize)
424  return ERROR_INT("&size not defined", procName, 1 );
425  *psize = 0;
426  if (!pix)
427  return ERROR_INT("&pix not defined", procName, 1 );
428 
429  if ((buffer = bbufferCreate(NULL, 0)) == NULL)
430  return ERROR_INT("failed to create buffer", procName, 1);
431 
432  if ((gif = EGifOpen((void*)buffer, gifWriteFunc, NULL)) == NULL) {
433  bbufferDestroy(&buffer);
434  return ERROR_INT("failed to create GIF image handle", procName, 1);
435  }
436 
437  result = pixToGif(pix, gif);
438  EGifCloseFile(gif, &giferr);
439 
440  if (result == 0) {
441  *pdata = bbufferDestroyAndSaveData(&buffer, psize);
442  } else {
443  bbufferDestroy(&buffer);
444  }
445  return result;
446 }
447 
448 
449 static l_int32
450 gifWriteFunc(GifFileType *gif,
451  const GifByteType *src,
452  l_int32 bytesToWrite)
453 {
454 L_BBUFFER *buffer;
455 
456  PROCNAME("gifWriteFunc");
457 
458  if ((buffer = (L_BBUFFER*)gif->UserData) == NULL)
459  return ERROR_INT("UserData not set", procName, -1);
460 
461  if(bbufferRead(buffer, (l_uint8*)src, bytesToWrite) == 0)
462  return bytesToWrite;
463  return 0;
464 }
465 
466 
481 static l_int32
482 pixToGif(PIX *pix,
483  GifFileType *gif)
484 {
485 char *text;
486 l_int32 wpl, i, j, w, h, d, ncolor, rval, gval, bval;
487 l_int32 gif_ncolor = 0;
488 l_uint32 *data, *line;
489 PIX *pixd;
490 PIXCMAP *cmap;
491 ColorMapObject *gif_cmap;
492 GifByteType *gif_line;
493 
494  PROCNAME("pixToGif");
495 
496  if (!pix)
497  return ERROR_INT("pix not defined", procName, 1);
498  if (!gif)
499  return ERROR_INT("gif not defined", procName, 1);
500 
501  d = pixGetDepth(pix);
502  if (d == 32) {
503  pixd = pixConvertRGBToColormap(pix, 1);
504  } else if (d > 1) {
505  pixd = pixConvertTo8(pix, TRUE);
506  } else { /* d == 1; make sure there's a colormap */
507  pixd = pixClone(pix);
508  if (!pixGetColormap(pixd)) {
509  cmap = pixcmapCreate(1);
510  pixcmapAddColor(cmap, 255, 255, 255);
511  pixcmapAddColor(cmap, 0, 0, 0);
512  pixSetColormap(pixd, cmap);
513  }
514  }
515 
516  if (!pixd)
517  return ERROR_INT("failed to convert image to indexed", procName, 1);
518  d = pixGetDepth(pixd);
519 
520  if ((cmap = pixGetColormap(pixd)) == NULL) {
521  pixDestroy(&pixd);
522  return ERROR_INT("cmap is missing", procName, 1);
523  }
524 
525  /* 'Round' the number of gif colors up to a power of 2 */
526  ncolor = pixcmapGetCount(cmap);
527  for (i = 0; i <= 8; i++) {
528  if ((1 << i) >= ncolor) {
529  gif_ncolor = (1 << i);
530  break;
531  }
532  }
533  if (gif_ncolor < 1) {
534  pixDestroy(&pixd);
535  return ERROR_INT("number of colors is invalid", procName, 1);
536  }
537 
538  /* Save the cmap colors in a gif_cmap */
539  if ((gif_cmap = GifMakeMapObject(gif_ncolor, NULL)) == NULL) {
540  pixDestroy(&pixd);
541  return ERROR_INT("failed to create GIF color map", procName, 1);
542  }
543  for (i = 0; i < gif_ncolor; i++) {
544  rval = gval = bval = 0;
545  if (ncolor > 0) {
546  if (pixcmapGetColor(cmap, i, &rval, &gval, &bval) != 0) {
547  pixDestroy(&pixd);
548  GifFreeMapObject(gif_cmap);
549  return ERROR_INT("failed to get color from color map",
550  procName, 1);
551  }
552  ncolor--;
553  }
554  gif_cmap->Colors[i].Red = rval;
555  gif_cmap->Colors[i].Green = gval;
556  gif_cmap->Colors[i].Blue = bval;
557  }
558 
559  pixGetDimensions(pixd, &w, &h, NULL);
560  if (EGifPutScreenDesc(gif, w, h, gif_cmap->BitsPerPixel, 0, gif_cmap)
561  != GIF_OK) {
562  pixDestroy(&pixd);
563  GifFreeMapObject(gif_cmap);
564  return ERROR_INT("failed to write screen description", procName, 1);
565  }
566  GifFreeMapObject(gif_cmap); /* not needed after this point */
567 
568  if (EGifPutImageDesc(gif, 0, 0, w, h, FALSE, NULL) != GIF_OK) {
569  pixDestroy(&pixd);
570  return ERROR_INT("failed to image screen description", procName, 1);
571  }
572 
573  data = pixGetData(pixd);
574  wpl = pixGetWpl(pixd);
575  if (d != 1 && d != 2 && d != 4 && d != 8) {
576  pixDestroy(&pixd);
577  return ERROR_INT("image depth is not in {1, 2, 4, 8}", procName, 1);
578  }
579 
580  if ((gif_line = (GifByteType *)LEPT_CALLOC(sizeof(GifByteType), w))
581  == NULL) {
582  pixDestroy(&pixd);
583  return ERROR_INT("mem alloc fail for data line", procName, 1);
584  }
585 
586  for (i = 0; i < h; i++) {
587  line = data + i * wpl;
588  /* Gif's way of setting the raster line up for compression */
589  for (j = 0; j < w; j++) {
590  switch(d)
591  {
592  case 8:
593  gif_line[j] = GET_DATA_BYTE(line, j);
594  break;
595  case 4:
596  gif_line[j] = GET_DATA_QBIT(line, j);
597  break;
598  case 2:
599  gif_line[j] = GET_DATA_DIBIT(line, j);
600  break;
601  case 1:
602  gif_line[j] = GET_DATA_BIT(line, j);
603  break;
604  }
605  }
606 
607  /* Compress and save the line */
608  if (EGifPutLine(gif, gif_line, w) != GIF_OK) {
609  LEPT_FREE(gif_line);
610  pixDestroy(&pixd);
611  return ERROR_INT("failed to write data line into GIF", procName, 1);
612  }
613  }
614 
615  /* Write a text comment. This must be placed after writing the
616  * data (!!) Note that because libgif does not provide a function
617  * for reading comments from file, you will need another way
618  * to read comments. */
619  if ((text = pixGetText(pix)) != NULL) {
620  if (EGifPutComment(gif, text) != GIF_OK)
621  L_WARNING("gif comment not written\n", procName);
622  }
623 
624  LEPT_FREE(gif_line);
625  pixDestroy(&pixd);
626  return 0;
627 }
628 
629 
630 #if 0
631 /*---------------------------------------------------------------------*
632  * Removing interlacing (reference only; not used) *
633  *---------------------------------------------------------------------*/
634  /* GIF supports 4-way interlacing by raster lines.
635  * Before 5.0, it was necessary for leptonica to restore interlaced
636  * data to normal raster order when reading to a pix. With 5.0,
637  * the de-interlacing is done by the library read function.
638  * It is here only as a reference. */
639 static const l_int32 InterlacedOffset[] = {0, 4, 2, 1};
640 static const l_int32 InterlacedJumps[] = {8, 8, 4, 2};
641 
642 static PIX *
643 pixUninterlaceGIF(PIX *pixs)
644 {
645 l_int32 w, h, d, wpl, j, k, srow, drow;
646 l_uint32 *datas, *datad, *lines, *lined;
647 PIX *pixd;
648 
649  PROCNAME("pixUninterlaceGIF");
650 
651  if (!pixs)
652  return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
653 
654  pixGetDimensions(pixs, &w, &h, &d);
655  wpl = pixGetWpl(pixs);
656  pixd = pixCreateTemplate(pixs);
657  datas = pixGetData(pixs);
658  datad = pixGetData(pixd);
659  for (k = 0, srow = 0; k < 4; k++) {
660  for (drow = InterlacedOffset[k]; drow < h;
661  drow += InterlacedJumps[k], srow++) {
662  lines = datas + srow * wpl;
663  lined = datad + drow * wpl;
664  for (j = 0; j < w; j++)
665  memcpy(lined, lines, 4 * wpl);
666  }
667  }
668 
669  return pixd;
670 }
671 #endif
672 
673 
674 /* -----------------------------------------------------------------*/
675 #endif /* HAVE_LIBGIF || HAVE_LIBUNGIF */
L_BBUFFER * bbufferCreate(const l_uint8 *indata, l_int32 nalloc)
bbufferCreate()
Definition: bbuffer.c:124
PIX * pixConvertTo8(PIX *pixs, l_int32 cmapflag)
pixConvertTo8()
Definition: pixconv.c:3041
PIX * pixCreate(l_int32 width, l_int32 height, l_int32 depth)
pixCreate()
Definition: pix1.c:302
#define SET_DATA_QBIT(pdata, n, val)
Definition: arrayaccess.h:168
void pixcmapDestroy(PIXCMAP **pcmap)
pixcmapDestroy()
Definition: colormap.c:265
l_uint32 * pixGetData(PIX *pix)
pixGetData()
Definition: pix1.c:1624
#define GET_DATA_BIT(pdata, n)
Definition: arrayaccess.h:123
PIX * pixCreateTemplate(PIX *pixs)
pixCreateTemplate()
Definition: pix1.c:367
l_ok pixSetColormap(PIX *pix, PIXCMAP *colormap)
pixSetColormap()
Definition: pix1.c:1573
l_uint8 * bbufferDestroyAndSaveData(L_BBUFFER **pbb, size_t *pnbytes)
bbufferDestroyAndSaveData()
Definition: bbuffer.c:203
#define SET_DATA_DIBIT(pdata, n, val)
Definition: arrayaccess.h:149
PIXCMAP * pixcmapCreate(l_int32 depth)
pixcmapCreate()
Definition: colormap.c:111
l_ok pixcmapGetColor(PIXCMAP *cmap, l_int32 index, l_int32 *prval, l_int32 *pgval, l_int32 *pbval)
pixcmapGetColor()
Definition: colormap.c:751
#define SET_DATA_BYTE(pdata, n, val)
Definition: arrayaccess.h:198
l_ok pixSetPadBits(PIX *pix, l_int32 val)
pixSetPadBits()
Definition: pix2.c:1307
#define GET_DATA_QBIT(pdata, n)
Definition: arrayaccess.h:164
#define GET_DATA_BYTE(pdata, n)
Definition: arrayaccess.h:188
PIX * pixClone(PIX *pixs)
pixClone()
Definition: pix1.c:515
void pixDestroy(PIX **ppix)
pixDestroy()
Definition: pix1.c:543
void bbufferDestroy(L_BBUFFER **pbb)
bbufferDestroy()
Definition: bbuffer.c:167
l_ok bbufferRead(L_BBUFFER *bb, l_uint8 *src, l_int32 nbytes)
bbufferRead()
Definition: bbuffer.c:262
l_ok pixGetDimensions(const PIX *pix, l_int32 *pw, l_int32 *ph, l_int32 *pd)
pixGetDimensions()
Definition: pix1.c:1065
l_uint8 * l_binaryReadStream(FILE *fp, size_t *pnbytes)
l_binaryReadStream()
Definition: utils2.c:1262
char * pixGetText(PIX *pix)
pixGetText()
Definition: pix1.c:1459
#define GET_DATA_DIBIT(pdata, n)
Definition: arrayaccess.h:145
PIX * pixConvertRGBToColormap(PIX *pixs, l_int32 ditherflag)
pixConvertRGBToColormap()
Definition: pixconv.c:1412
l_int32 pixcmapGetCount(PIXCMAP *cmap)
pixcmapGetCount()
Definition: colormap.c:635
Definition: pix.h:134
l_ok pixcmapAddColor(PIXCMAP *cmap, l_int32 rval, l_int32 gval, l_int32 bval)
pixcmapAddColor()
Definition: colormap.c:341
#define SET_DATA_BIT(pdata, n)
Definition: arrayaccess.h:127