//========================================================================
//
// ImageOutputDev.cc
//
// Copyright 1998-2003 Glyph & Cog, LLC
//
//========================================================================

#include <aconf.h>

#ifdef USE_GCC_PRAGMAS
#pragma implementation
#endif

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <ctype.h>
#include <math.h>
#include "gmem.h"
#include "gmempp.h"
#include "config.h"
#include "Error.h"
#include "GfxState.h"
#include "Object.h"
#include "Stream.h"
#include "ImageOutputDev.h"

ImageOutputDev::ImageOutputDev(char *fileRootA, GBool dumpJPEGA,
			       GBool dumpRawA, GBool listA) {
  fileRoot = copyString(fileRootA);
  dumpJPEG = dumpJPEGA;
  dumpRaw = dumpRawA;
  list = listA;
  imgNum = 0;
  curPageNum = 0;
  ok = gTrue;
}

ImageOutputDev::~ImageOutputDev() {
  gfree(fileRoot);
}

void ImageOutputDev::startPage(int pageNum, GfxState *state) {
  curPageNum = pageNum;
}

void ImageOutputDev::tilingPatternFill(GfxState *state, Gfx *gfx,
				       Object *strRef,
				       int paintType, int tilingType,
				       Dict *resDict,
				       double *mat, double *bbox,
				       int x0, int y0, int x1, int y1,
				       double xStep, double yStep) {
  // do nothing -- this avoids the potentially slow loop in Gfx.cc
}

void ImageOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str,
				   int width, int height, GBool invert,
				   GBool inlineImg, GBool interpolate) {
  GString *fileName;
  FILE *f;
  char buf[4096];
  int size, n, i;

  // dump raw file
  if (dumpRaw && !inlineImg) {

    // open the image file
    fileName = GString::format("{0:s}-{1:04d}.{2:s}",
			       fileRoot, imgNum, getRawFileExtension(str));
    ++imgNum;
    if (!(f = openFile(fileName->getCString(), "wb"))) {
      error(errIO, -1, "Couldn't open image file '{0:t}'", fileName);
      delete fileName;
      return;
    }

    // initialize stream
    str = getRawStream(str);
    str->reset();

    // copy the stream
    while ((n = str->getBlock(buf, sizeof(buf))) > 0) {
      fwrite(buf, 1, n, f);
    }

    str->close();
    fclose(f);

  // dump JPEG file
  } else if (dumpJPEG && str->getKind() == strDCT && !inlineImg) {

    // open the image file
    fileName = GString::format("{0:s}-{1:04d}.jpg", fileRoot, imgNum);
    ++imgNum;
    if (!(f = openFile(fileName->getCString(), "wb"))) {
      error(errIO, -1, "Couldn't open image file '{0:t}'", fileName);
      delete fileName;
      return;
    }

    // initialize stream
    str = ((DCTStream *)str)->getRawStream();
    str->reset();

    // copy the stream
    while ((n = str->getBlock(buf, sizeof(buf))) > 0) {
      fwrite(buf, 1, n, f);
    }

    str->close();
    fclose(f);

  // dump PBM file
  } else {

    // open the image file and write the PBM header
    fileName = GString::format("{0:s}-{1:04d}.pbm", fileRoot, imgNum);
    ++imgNum;
    if (!(f = openFile(fileName->getCString(), "wb"))) {
      error(errIO, -1, "Couldn't open image file '{0:t}'", fileName);
      delete fileName;
      return;
    }
    fprintf(f, "P4\n");
    fprintf(f, "%d %d\n", width, height);

    // initialize stream
    str->reset();

    // copy the stream
    size = height * ((width + 7) / 8);
    while (size > 0) {
      i = size < (int)sizeof(buf) ? size : (int)sizeof(buf);
      n = str->getBlock(buf, i);
      fwrite(buf, 1, n, f);
      if (n < i) {
	break;
      }
      size -= n;
    }

    str->close();
    fclose(f);
  }

  if (list) {
    writeImageInfo(fileName, width, height, state, NULL);
  }

  delete fileName;
}

void ImageOutputDev::drawImage(GfxState *state, Object *ref, Stream *str,
			       int width, int height,
			       GfxImageColorMap *colorMap,
			       int *maskColors, GBool inlineImg,
			       GBool interpolate) {
  GfxColorSpaceMode csMode;
  GString *fileName;
  FILE *f;
  ImageStream *imgStr;
  Guchar *p;
  GfxRGB rgb;
  GfxGray gray;
  int x, y;
  char buf[4096];
  int size, n, i, j;

  csMode = colorMap->getColorSpace()->getMode();
  if (csMode == csIndexed) {
    csMode = ((GfxIndexedColorSpace *)colorMap->getColorSpace())
             ->getBase()->getMode();
  }

  // dump raw file
  if (dumpRaw && !inlineImg) {

    // open the image file
    fileName = GString::format("{0:s}-{1:04d}.{2:s}",
			       fileRoot, imgNum, getRawFileExtension(str));
    ++imgNum;
    if (!(f = openFile(fileName->getCString(), "wb"))) {
      error(errIO, -1, "Couldn't open image file '{0:t}'", fileName);
      delete fileName;
      return;
    }

    // initialize stream
    str = getRawStream(str);
    str->reset();

    // copy the stream
    while ((n = str->getBlock(buf, sizeof(buf))) > 0) {
      fwrite(buf, 1, n, f);
    }

    str->close();
    fclose(f);

  // dump JPEG file
  } else if (dumpJPEG && str->getKind() == strDCT &&
	     (colorMap->getNumPixelComps() == 1 ||
	      colorMap->getNumPixelComps() == 3) &&
	     !inlineImg) {

    // open the image file
    fileName = GString::format("{0:s}-{1:04d}.jpg", fileRoot, imgNum);
    ++imgNum;
    if (!(f = openFile(fileName->getCString(), "wb"))) {
      error(errIO, -1, "Couldn't open image file '{0:t}'", fileName);
      delete fileName;
      return;
    }

    // initialize stream
    str = ((DCTStream *)str)->getRawStream();
    str->reset();

    // copy the stream
    while ((n = str->getBlock(buf, sizeof(buf))) > 0) {
      fwrite(buf, 1, n, f);
    }

    str->close();
    fclose(f);

  // dump PBM file
  } else if (colorMap->getNumPixelComps() == 1 &&
	     colorMap->getBits() == 1) {

    // open the image file and write the PBM header
    fileName = GString::format("{0:s}-{1:04d}.pbm", fileRoot, imgNum);
    ++imgNum;
    if (!(f = openFile(fileName->getCString(), "wb"))) {
      error(errIO, -1, "Couldn't open image file '{0:t}'", fileName);
      delete fileName;
      return;
    }
    fprintf(f, "P4\n");
    fprintf(f, "%d %d\n", width, height);

    // initialize stream
    str->reset();

    // copy the stream
    size = height * ((width + 7) / 8);
    while (size > 0) {
      i = size < (int)sizeof(buf) ? size : (int)sizeof(buf);
      n = str->getBlock(buf, i);
      for (j = 0; j < n; ++j) {
	buf[j] = (char)(buf[j] ^ 0xff);
      }
      fwrite(buf, 1, n, f);
      if (n < i) {
	break;
      }
      size -= n;
    }

    str->close();
    fclose(f);

  // dump PGM file
  } else if (colorMap->getNumPixelComps() == 1 &&
	     (csMode == csDeviceGray || csMode == csCalGray)) {

    // open the image file and write the PGM header
    fileName = GString::format("{0:s}-{1:04d}.pgm", fileRoot, imgNum);
    ++imgNum;
    if (!(f = openFile(fileName->getCString(), "wb"))) {
      error(errIO, -1, "Couldn't open image file '{0:t}'", fileName);
      delete fileName;
      return;
    }
    fprintf(f, "P5\n");
    fprintf(f, "%d %d\n", width, height);
    fprintf(f, "255\n");

    // initialize stream
    imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(),
			     colorMap->getBits());
    imgStr->reset();

    // for each line...
    for (y = 0; y < height; ++y) {

      // write the line
      if ((p = imgStr->getLine())) {
	for (x = 0; x < width; ++x) {
	  colorMap->getGray(p, &gray, state->getRenderingIntent());
	  fputc(colToByte(gray), f);
	  ++p;
	}
      } else {
	for (x = 0; x < width; ++x) {
	  fputc(0, f);
	}
      }
    }

    imgStr->close();
    delete imgStr;

    fclose(f);

  // dump PPM file
  } else {

    // open the image file and write the PPM header
    fileName = GString::format("{0:s}-{1:04d}.ppm", fileRoot, imgNum);
    ++imgNum;
    if (!(f = openFile(fileName->getCString(), "wb"))) {
      error(errIO, -1, "Couldn't open image file '{0:t}'", fileName);
      delete fileName;
      return;
    }
    fprintf(f, "P6\n");
    fprintf(f, "%d %d\n", width, height);
    fprintf(f, "255\n");

    // initialize stream
    imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(),
			     colorMap->getBits());
    imgStr->reset();

    // for each line...
    for (y = 0; y < height; ++y) {

      // write the line
      if ((p = imgStr->getLine())) {
	for (x = 0; x < width; ++x) {
	  colorMap->getRGB(p, &rgb, state->getRenderingIntent());
	  fputc(colToByte(rgb.r), f);
	  fputc(colToByte(rgb.g), f);
	  fputc(colToByte(rgb.b), f);
	  p += colorMap->getNumPixelComps();
	}
      } else {
	for (x = 0; x < width; ++x) {
	  fputc(0, f);
	  fputc(0, f);
	  fputc(0, f);
	}
      }
    }

    imgStr->close();
    delete imgStr;

    fclose(f);
  }

  if (list) {
    writeImageInfo(fileName, width, height, state, colorMap);
  }

  delete fileName;
}

void ImageOutputDev::drawMaskedImage(GfxState *state, Object *ref, Stream *str,
				     int width, int height,
				     GfxImageColorMap *colorMap,
				     Object *maskRef, Stream *maskStr,
				     int maskWidth, int maskHeight,
				     GBool maskInvert, GBool interpolate) {
  drawImage(state, ref, str, width, height, colorMap,
	    NULL, gFalse, interpolate);
  drawImageMask(state, maskRef, maskStr, maskWidth, maskHeight, maskInvert,
		gFalse, interpolate);
}

void ImageOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref,
					 Stream *str,
					 int width, int height,
					 GfxImageColorMap *colorMap,
					 Object *maskRef, Stream *maskStr,
					 int maskWidth, int maskHeight,
					 GfxImageColorMap *maskColorMap,
					 double *matte, GBool interpolate) {
  drawImage(state, ref, str, width, height, colorMap,
	    NULL, gFalse, interpolate);
  drawImage(state, maskRef, maskStr, maskWidth, maskHeight, maskColorMap,
	    NULL, gFalse, interpolate);
}

Stream *ImageOutputDev::getRawStream(Stream *str) {
  switch (str->getKind()) {
  case strLZW:
  case strRunLength:
  case strCCITTFax:
  case strDCT:
  case strFlate:
  case strJBIG2:
  case strJPX:
    return ((FilterStream *)str)->getNextStream();
  default:
    return str;
  }
}

const char *ImageOutputDev::getRawFileExtension(Stream *str) {
  switch (str->getKind()) {
  case strLZW:       return "lzw";
  case strRunLength: return "rle";
  case strCCITTFax:  return "fax";
  case strDCT:       return "jpg";
  case strFlate:     return "flate";
  case strJBIG2:     return "jb2";
  case strJPX:       return "jpx";
  default:           return "unknown";
  }
}

void ImageOutputDev::writeImageInfo(GString *fileName,
				    int width, int height, GfxState *state,
				    GfxImageColorMap *colorMap) {
  const char *mode;
  double hdpi, vdpi, x0, y0, x1, y1;
  int bpc;

  // this works for 0/90/180/270-degree rotations, along with
  // horizontal/vertical flips
  state->transformDelta(1, 0, &x0, &y0);
  state->transformDelta(0, 1, &x1, &y1);
  x0 = fabs(x0);
  y0 = fabs(y0);
  x1 = fabs(x1);
  y1 = fabs(y1);
  if (x0 > y0) {
    hdpi = (72 * width) / x0;
    vdpi = (72 * height) / y1;
  } else {
    hdpi = (72 * height) / x1;
    vdpi = (72 * width) / y0;
  }

  if (colorMap) {
    mode = GfxColorSpace::getColorSpaceModeName(
			      colorMap->getColorSpace()->getMode());
    bpc = colorMap->getBits();
  } else {
    mode = NULL;
    bpc = 1;
  }

  printf("%s: page=%d width=%d height=%d hdpi=%.2f vdpi=%.2f %s%s bpc=%d\n",
	 fileName->getCString(), curPageNum, width, height, hdpi, vdpi,
	 mode ? "colorspace=" : "mask",
	 mode ? mode : "",
	 bpc);
}
