CairoOutputDev.cc 32 KB
Newer Older
Kristian Høgsberg's avatar
Kristian Høgsberg committed
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
//========================================================================
//
// CairoOutputDev.cc
//
// Copyright 2003 Glyph & Cog, LLC
// Copyright 2004 Red Hat, Inc
//
//========================================================================

#include <config.h>

#ifdef USE_GCC_PRAGMAS
#pragma implementation
#endif

#include <string.h>
#include <math.h>
#include <cairo.h>

#include "goo/gfile.h"
#include "GlobalParams.h"
#include "Error.h"
#include "Object.h"
#include "GfxState.h"
#include "GfxFont.h"
#include "Link.h"
#include "CharCodeToUnicode.h"
#include "FontEncodingTables.h"
#include <fofi/FoFiTrueType.h>
#include <splash/SplashBitmap.h>
#include "CairoOutputDev.h"
#include "CairoFontEngine.h"

//------------------------------------------------------------------------

36
// #define LOG_CAIRO
Kristian Høgsberg's avatar
Kristian Høgsberg committed
37 38 39 40 41 42 43

#ifdef LOG_CAIRO
#define LOG(x) (x)
#else
#define LOG(x)
#endif

44 45 46 47 48 49
static inline void printMatrix(cairo_matrix_t *matrix){
	printf("%f %f, %f %f (%f %f)\n", matrix->xx, matrix->yx,
			matrix->xy, matrix->yy,
			matrix->x0, matrix->y0);
}

50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
//------------------------------------------------------------------------
// CairoImage
//------------------------------------------------------------------------

CairoImage::CairoImage (cairo_surface_t *image,
			double x1, double y1, double x2, double y2) {
  this->image = cairo_surface_reference (image);
  this->x1 = x1;
  this->y1 = y1;
  this->x2 = x2;
  this->y2 = y2;
}

CairoImage::~CairoImage () {
  if (image)
    cairo_surface_destroy (image);
}
Kristian Høgsberg's avatar
Kristian Høgsberg committed
67 68 69 70 71

//------------------------------------------------------------------------
// CairoOutputDev
//------------------------------------------------------------------------

72
CairoOutputDev::CairoOutputDev() {
Kristian Høgsberg's avatar
Kristian Høgsberg committed
73 74 75 76
  xref = NULL;

  FT_Init_FreeType(&ft_lib);
  fontEngine = NULL;
77
  glyphs = NULL;
78 79
  fill_pattern = NULL;
  stroke_pattern = NULL;
80 81
  stroke_opacity = 1.0;
  fill_opacity = 1.0;
82
  textClipPath = NULL;
83
  cairo = NULL;
84
  currentFont = NULL;
85 86 87 88

  groupColorSpaceStack = NULL;
  group = NULL;
  mask = NULL;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
89 90 91 92 93 94 95
}

CairoOutputDev::~CairoOutputDev() {
  if (fontEngine) {
    delete fontEngine;
  }
  FT_Done_FreeType(ft_lib);
96 97 98
  
  if (cairo)
    cairo_destroy (cairo);
99 100
  cairo_pattern_destroy (stroke_pattern);
  cairo_pattern_destroy (fill_pattern);
101
}
Kristian Høgsberg's avatar
Kristian Høgsberg committed
102

103
void CairoOutputDev::setCairo(cairo_t *cairo)
104
{
105 106
  if (this->cairo != NULL)
    cairo_destroy (this->cairo);
107
  if (cairo != NULL) {
108
    this->cairo = cairo_reference (cairo);
109 110 111 112
	/* save the initial matrix so that we can use it for type3 fonts. */
	//XXX: is this sufficient? could we miss changes to the matrix somehow?
	cairo_get_matrix(cairo, &orig_matrix);
  } else {
113
    this->cairo = NULL;
114
  }
Kristian Høgsberg's avatar
Kristian Høgsberg committed
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
}

void CairoOutputDev::startDoc(XRef *xrefA) {
  xref = xrefA;
  if (fontEngine) {
    delete fontEngine;
  }
  fontEngine = new CairoFontEngine(ft_lib);
}

void CairoOutputDev::drawLink(Link *link, Catalog *catalog) {
}

void CairoOutputDev::saveState(GfxState *state) {
  LOG(printf ("save\n"));
  cairo_save (cairo);
}

void CairoOutputDev::restoreState(GfxState *state) {
  LOG(printf ("restore\n"));
  cairo_restore (cairo);
136 137 138 139 140 141 142

  /* These aren't restored by cairo_restore() since we keep them in
   * the output device. */
  updateFillColor(state);
  updateStrokeColor(state);
  updateFillOpacity(state);
  updateStrokeOpacity(state);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
143 144 145 146 147 148 149 150 151 152 153
}

void CairoOutputDev::updateAll(GfxState *state) {
  updateLineDash(state);
  updateLineJoin(state);
  updateLineCap(state);
  updateLineWidth(state);
  updateFlatness(state);
  updateMiterLimit(state);
  updateFillColor(state);
  updateStrokeColor(state);
154 155
  updateFillOpacity(state);
  updateStrokeOpacity(state);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
156 157 158
  needFontUpdate = gTrue;
}

159 160 161 162 163 164 165 166 167
void CairoOutputDev::setDefaultCTM(double *ctm) {
  cairo_matrix_t matrix;
  matrix.xx = ctm[0];
  matrix.yx = ctm[1];
  matrix.xy = ctm[2];
  matrix.yy = ctm[3];
  matrix.x0 = ctm[4];
  matrix.y0 = ctm[5];

168
  cairo_transform (cairo, &matrix);
169 170 171 172

  OutputDev::setDefaultCTM(ctm);
}

Kristian Høgsberg's avatar
Kristian Høgsberg committed
173 174 175
void CairoOutputDev::updateCTM(GfxState *state, double m11, double m12,
				double m21, double m22,
				double m31, double m32) {
176 177 178 179 180 181 182 183 184
  cairo_matrix_t matrix;
  matrix.xx = m11;
  matrix.yx = m12;
  matrix.xy = m21;
  matrix.yy = m22;
  matrix.x0 = m31;
  matrix.y0 = m32;

  cairo_transform (cairo, &matrix);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
185 186 187 188 189 190 191 192 193 194 195 196
  updateLineDash(state);
  updateLineJoin(state);
  updateLineCap(state);
  updateLineWidth(state);
}

void CairoOutputDev::updateLineDash(GfxState *state) {
  double *dashPattern;
  int dashLength;
  double dashStart;

  state->getLineDash(&dashPattern, &dashLength, &dashStart);
197
  cairo_set_dash (cairo, dashPattern, dashLength, dashStart);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
198 199 200
}

void CairoOutputDev::updateFlatness(GfxState *state) {
201
  // cairo_set_tolerance (cairo, state->getFlatness());
Kristian Høgsberg's avatar
Kristian Høgsberg committed
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
}

void CairoOutputDev::updateLineJoin(GfxState *state) {
  switch (state->getLineJoin()) {
  case 0:
    cairo_set_line_join (cairo, CAIRO_LINE_JOIN_MITER);
    break;
  case 1:
    cairo_set_line_join (cairo, CAIRO_LINE_JOIN_ROUND);
    break;
  case 2:
    cairo_set_line_join (cairo, CAIRO_LINE_JOIN_BEVEL);
    break;
  }
}

void CairoOutputDev::updateLineCap(GfxState *state) {
  switch (state->getLineCap()) {
  case 0:
    cairo_set_line_cap (cairo, CAIRO_LINE_CAP_BUTT);
    break;
  case 1:
    cairo_set_line_cap (cairo, CAIRO_LINE_CAP_ROUND);
    break;
  case 2:
    cairo_set_line_cap (cairo, CAIRO_LINE_CAP_SQUARE);
    break;
  }
}

void CairoOutputDev::updateMiterLimit(GfxState *state) {
  cairo_set_miter_limit (cairo, state->getMiterLimit());
}

236 237
#define MIN(a,b) (a) < (b) ? (a) : (b)

Kristian Høgsberg's avatar
Kristian Høgsberg committed
238
void CairoOutputDev::updateLineWidth(GfxState *state) {
239 240
  LOG(printf ("line width: %f\n", state->getLineWidth()));
  if (state->getLineWidth() == 0.0) {
241 242 243 244 245
    /* find out how big pixels (device unit) are in the x and y directions
     * choose the smaller of the two as our line width */
    double x = 1.0, y = 1.0;
    cairo_device_to_user_distance(cairo, &x, &y);
    cairo_set_line_width (cairo, MIN(fabs(x),fabs(y)));
246
  } else {
247
    cairo_set_line_width (cairo, state->getLineWidth());
248
  }
Kristian Høgsberg's avatar
Kristian Høgsberg committed
249 250 251 252
}

void CairoOutputDev::updateFillColor(GfxState *state) {
  state->getFillRGB(&fill_color);
253 254 255 256 257 258 259 260 261

  cairo_pattern_destroy(fill_pattern);
  fill_pattern = cairo_pattern_create_rgba(fill_color.r / 65535.0,
					   fill_color.g / 65535.0,
					   fill_color.b / 65535.0,
					   fill_opacity);

  LOG(printf ("fill color: %d %d %d\n",
	      fill_color.r, fill_color.g, fill_color.b));
Kristian Høgsberg's avatar
Kristian Høgsberg committed
262 263 264 265
}

void CairoOutputDev::updateStrokeColor(GfxState *state) {
  state->getStrokeRGB(&stroke_color);
266 267 268 269 270 271 272 273 274

  cairo_pattern_destroy(stroke_pattern);
  stroke_pattern = cairo_pattern_create_rgba(stroke_color.r / 65535.0,
					     stroke_color.g / 65535.0,
					     stroke_color.b / 65535.0,
					     stroke_opacity);
  
  LOG(printf ("stroke color: %d %d %d\n",
	      stroke_color.r, stroke_color.g, stroke_color.b));
Kristian Høgsberg's avatar
Kristian Høgsberg committed
275 276
}

277 278
void CairoOutputDev::updateFillOpacity(GfxState *state) {
  fill_opacity = state->getFillOpacity();
279 280 281 282 283 284 285

  cairo_pattern_destroy(fill_pattern);
  fill_pattern = cairo_pattern_create_rgba(fill_color.r / 65535.0,
					   fill_color.g / 65535.0,
					   fill_color.b / 65535.0,
					   fill_opacity);

286 287 288 289 290
  LOG(printf ("fill opacity: %f\n", fill_opacity));
}

void CairoOutputDev::updateStrokeOpacity(GfxState *state) {
  stroke_opacity = state->getStrokeOpacity();
291 292 293 294 295 296 297

  cairo_pattern_destroy(stroke_pattern);
  stroke_pattern = cairo_pattern_create_rgba(stroke_color.r / 65535.0,
					     stroke_color.g / 65535.0,
					     stroke_color.b / 65535.0,
					     stroke_opacity);
  
298 299 300
  LOG(printf ("stroke opacity: %f\n", stroke_opacity));
}

Kristian Høgsberg's avatar
Kristian Høgsberg committed
301
void CairoOutputDev::updateFont(GfxState *state) {
302 303
  cairo_font_face_t *font_face;
  cairo_matrix_t matrix;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
304 305

  LOG(printf ("updateFont() font=%s\n", state->getFont()->getName()->getCString()));
306

Kristian Høgsberg's avatar
Kristian Høgsberg committed
307 308
  needFontUpdate = gFalse;

309 310 311
  if (state->getFont()->getType() == fontType3)	 
    return;

312 313
  currentFont = fontEngine->getFont (state->getFont(), xref);

314 315
  if (!currentFont)
    return;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
316

317
  LOG(printf ("font matrix: %f %f %f %f\n", m11, m12, m21, m22));
Kristian Høgsberg's avatar
Kristian Høgsberg committed
318
  
319 320
  font_face = currentFont->getFontFace();
  cairo_set_font_face (cairo, font_face);
321 322 323
 
  double fontSize = state->getFontSize();
  double *m = state->getTextMat();
324 325
  matrix.xx = m[0] * fontSize * state->getHorizScaling();
  matrix.yx = m[1] * fontSize * state->getHorizScaling();
326 327
  matrix.xy = -m[2] * fontSize;
  matrix.yy = -m[3] * fontSize;
328 329
  matrix.x0 = 0;
  matrix.y0 = 0;
330
  cairo_set_font_matrix (cairo, &matrix);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
331 332
}

333
void CairoOutputDev::doPath(GfxState *state, GfxPath *path) {
Kristian Høgsberg's avatar
Kristian Høgsberg committed
334 335 336 337 338
  GfxSubpath *subpath;
  int i, j;
  for (i = 0; i < path->getNumSubpaths(); ++i) {
    subpath = path->getSubpath(i);
    if (subpath->getNumPoints() > 0) {
339 340
      cairo_move_to (cairo, subpath->getX(0), subpath->getY(0));
         j = 1;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
341 342
      while (j < subpath->getNumPoints()) {
	if (subpath->getCurve(j)) {
343 344 345 346 347
	  cairo_curve_to( cairo,
			  subpath->getX(j), subpath->getY(j),
			  subpath->getX(j+1), subpath->getY(j+1),
			  subpath->getX(j+2), subpath->getY(j+2));

Kristian Høgsberg's avatar
Kristian Høgsberg committed
348 349
	  j += 3;
	} else {
350
	  cairo_line_to (cairo, subpath->getX(j), subpath->getY(j));
Kristian Høgsberg's avatar
Kristian Høgsberg committed
351 352 353 354 355 356 357 358 359 360 361 362
	  ++j;
	}
      }
      if (subpath->isClosed()) {
	LOG (printf ("close\n"));
	cairo_close_path (cairo);
      }
    }
  }
}

void CairoOutputDev::stroke(GfxState *state) {
363
  doPath (state, state->getPath());
364
  cairo_set_source (cairo, stroke_pattern);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
365 366 367 368 369
  LOG(printf ("stroke\n"));
  cairo_stroke (cairo);
}

void CairoOutputDev::fill(GfxState *state) {
370
  doPath (state, state->getPath());
Kristian Høgsberg's avatar
Kristian Høgsberg committed
371
  cairo_set_fill_rule (cairo, CAIRO_FILL_RULE_WINDING);
372
  cairo_set_source (cairo, fill_pattern);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
373 374 375 376 377
  LOG(printf ("fill\n"));
  cairo_fill (cairo);
}

void CairoOutputDev::eoFill(GfxState *state) {
378
  doPath (state, state->getPath());
Kristian Høgsberg's avatar
Kristian Høgsberg committed
379
  cairo_set_fill_rule (cairo, CAIRO_FILL_RULE_EVEN_ODD);
380
  cairo_set_source (cairo, fill_pattern);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
381 382 383 384
  LOG(printf ("fill-eo\n"));
  cairo_fill (cairo);
}

385
void CairoOutputDev::clip(GfxState *state) {
386
  doPath (state, state->getPath());
Kristian Høgsberg's avatar
Kristian Høgsberg committed
387 388 389 390 391 392
  cairo_set_fill_rule (cairo, CAIRO_FILL_RULE_WINDING);
  cairo_clip (cairo);
  LOG (printf ("clip\n"));
}

void CairoOutputDev::eoClip(GfxState *state) {
393
  doPath (state, state->getPath());
Kristian Høgsberg's avatar
Kristian Høgsberg committed
394 395 396 397 398
  cairo_set_fill_rule (cairo, CAIRO_FILL_RULE_EVEN_ODD);
  cairo_clip (cairo);
  LOG (printf ("clip-eo\n"));
}

399
void CairoOutputDev::beginString(GfxState *state, GooString *s)
400 401
{
  int len = s->getLength();
402

403 404 405
  if (needFontUpdate)
    updateFont(state);

406 407 408 409 410 411 412
  glyphs = (cairo_glyph_t *) gmalloc (len * sizeof (cairo_glyph_t));
  glyphCount = 0;
}

void CairoOutputDev::drawChar(GfxState *state, double x, double y,
			      double dx, double dy,
			      double originX, double originY,
413
			      CharCode code, int nBytes, Unicode *u, int uLen)
414
{
415 416 417
  if (!currentFont)
    return;
  
418
  glyphs[glyphCount].index = currentFont->getGlyph (code, u, uLen);
419 420
  glyphs[glyphCount].x = x - originX;
  glyphs[glyphCount].y = y - originY;
421 422 423 424 425 426 427 428
  glyphCount++;
}

void CairoOutputDev::endString(GfxState *state)
{
  int render;

  if (!currentFont)
Kristian Høgsberg's avatar
Kristian Høgsberg committed
429
    return;
430 431 432 433 434 435 436

  // endString can be called without a corresponding beginString. If this
  // happens glyphs will be null so don't draw anything, just return.
  // XXX: OutputDevs should probably not have to deal with this...
  if (!glyphs)
    return;

437 438
  // ignore empty strings and invisible text -- this is used by
  // Acrobat Capture
Kristian Høgsberg's avatar
Kristian Høgsberg committed
439
  render = state->getRender();
440
  if (render == 3 || glyphCount == 0) {
441 442
    gfree(glyphs);
    glyphs = NULL;
443
    return;
444
  }
445
  
Kristian Høgsberg's avatar
Kristian Høgsberg committed
446
  if (!(render & 1)) {
447
    LOG (printf ("fill string\n"));
448
    cairo_set_source (cairo, fill_pattern);
449
    cairo_show_glyphs (cairo, glyphs, glyphCount);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
450
  }
451
  
Kristian Høgsberg's avatar
Kristian Høgsberg committed
452 453
  // stroke
  if ((render & 3) == 1 || (render & 3) == 2) {
454
    LOG (printf ("stroke string\n"));
455
    cairo_set_source (cairo, stroke_pattern);
456
    cairo_glyph_path (cairo, glyphs, glyphCount);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
457 458 459 460 461
    cairo_stroke (cairo);
  }

  // clip
  if (render & 4) {
462
    LOG (printf ("clip string\n"));
463 464 465 466 467 468 469 470 471
    // append the glyph path to textClipPath.

    // set textClipPath as the currentPath
    if (textClipPath) {
      cairo_append_path (cairo, textClipPath);
      cairo_path_destroy (textClipPath);
    }
    
    // append the glyph path
472
    cairo_glyph_path (cairo, glyphs, glyphCount);
473 474 475 476 477
   
    // move the path back into textClipPath 
    // and clear the current path
    textClipPath = cairo_copy_path (cairo);
    cairo_new_path (cairo);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
478
  }
479

480
  gfree (glyphs);
481
  glyphs = NULL;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
482 483
}

484

Kristian Høgsberg's avatar
Kristian Høgsberg committed
485 486 487
GBool CairoOutputDev::beginType3Char(GfxState *state, double x, double y,
				      double dx, double dy,
				      CharCode code, Unicode *u, int uLen) {
488 489 490 491 492 493 494 495 496 497 498 499

  cairo_save (cairo);
  double *ctm;
  cairo_matrix_t matrix;

  ctm = state->getCTM();
  matrix.xx = ctm[0];
  matrix.yx = ctm[1];
  matrix.xy = ctm[2];
  matrix.yy = ctm[3];
  matrix.x0 = ctm[4];
  matrix.y0 = ctm[5];
500 501 502 503
  /* Restore the original matrix and then transform to matrix needed for the
   * type3 font. This is ugly but seems to work. Perhaps there is a better way to do it?*/
  cairo_set_matrix(cairo, &orig_matrix);
  cairo_transform(cairo, &matrix);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
504 505 506 507
  return gFalse;
}

void CairoOutputDev::endType3Char(GfxState *state) {
508
  cairo_restore (cairo);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
509 510 511 512 513 514
}

void CairoOutputDev::type3D0(GfxState *state, double wx, double wy) {
}

void CairoOutputDev::type3D1(GfxState *state, double wx, double wy,
515
			     double llx, double lly, double urx, double ury) {
Kristian Høgsberg's avatar
Kristian Høgsberg committed
516 517 518
}

void CairoOutputDev::endTextObject(GfxState *state) {
519 520 521 522 523 524 525 526
  if (textClipPath) {
    // clip the accumulated text path
    cairo_append_path (cairo, textClipPath);
    cairo_clip (cairo);
    cairo_path_destroy (textClipPath);
    textClipPath = NULL;
  }

Kristian Høgsberg's avatar
Kristian Høgsberg committed
527 528
}

529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657
void CairoOutputDev::beginTransparencyGroup(GfxState * /*state*/, double * /*bbox*/,
                                      GfxColorSpace * blendingColorSpace,
                                      GBool /*isolated*/, GBool /*knockout*/,
				      GBool forSoftMask) {
  /* push color space */
  ColorSpaceStack* css = new ColorSpaceStack;
  css->cs = blendingColorSpace;
  css->next = groupColorSpaceStack;
  groupColorSpaceStack = css;

  if (0 && forSoftMask)
    cairo_push_group_with_content (cairo, CAIRO_CONTENT_ALPHA);
  else
    cairo_push_group (cairo);
}
void CairoOutputDev::endTransparencyGroup(GfxState * /*state*/) {
  if (group)
    cairo_pattern_destroy(group);
  group = cairo_pop_group (cairo);
}
void CairoOutputDev::paintTransparencyGroup(GfxState * /*state*/, double * /*bbox*/) {
  cairo_set_source (cairo, group);

  if (!mask) {
    cairo_paint_with_alpha (cairo, fill_opacity);
  } else {
    cairo_mask(cairo, mask);

    cairo_pattern_destroy(mask);
    mask = NULL;
  }

  /* pop color space */
  ColorSpaceStack *css = groupColorSpaceStack;
  groupColorSpaceStack = css->next;
  delete css;
}

typedef unsigned int uint32_t;

static uint32_t luminocity(uint32_t x)
{
  int a = (x >> 24) & 0xff;
  int r = (x >> 16) & 0xff;
  int g = (x >>  8) & 0xff;
  int b = (x >>  0) & 0xff;
  int y = 0.3 * r + 0.59 * g + 0.11 * b;
  return y << 24;
}


void CairoOutputDev::setSoftMask(GfxState * state, double * bbox, GBool alpha,
                                 Function * transferFunc, GfxColor * backdropColor) {
  if (alpha == false) {
    /* We need to mask according to the luminocity of the group.
     * So we paint the group to an image surface convert it to a luminocity map
     * and then use that as the mask. */

    double x1, y1, x2, y2;
    cairo_clip_extents(cairo, &x1, &y1, &x2, &y2);
    int width = ceil(x2) - floor(x1);
    int height = ceil(y2) - floor(y1);

    cairo_surface_t *source = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
    cairo_t *maskCtx = cairo_create(source);

    //XXX: hopefully this uses the correct color space */
    GfxRGB backdropColorRGB;
    groupColorSpaceStack->cs->getRGB(backdropColor, &backdropColorRGB);
    /* paint the backdrop */
    cairo_set_source_rgb(maskCtx, backdropColorRGB.r / 65535.0,
			 backdropColorRGB.g / 65535.0,
			 backdropColorRGB.b / 65535.0);


    cairo_matrix_t mat;
    cairo_get_matrix(cairo, &mat);
    cairo_set_matrix(maskCtx, &mat);

    /* make the device offset of the new mask match that of the group */
    double x_offset, y_offset;
    cairo_surface_t *pats;
    cairo_pattern_get_surface(group, &pats);
    cairo_surface_get_device_offset(pats, &x_offset, &y_offset);
    cairo_surface_set_device_offset(source, x_offset, y_offset);

    /* paint the group */
    cairo_set_source(maskCtx, group);
    cairo_paint(maskCtx);

    /* convert to a luminocity map */
    uint32_t *source_data = (uint32_t*)cairo_image_surface_get_data(source);
    /* get stride in units of 32 bits */
    int stride = cairo_image_surface_get_stride(source)/4;
    for (int y=0; y<height; y++) {
      for (int x=0; x<width; x++) {
	source_data[y*stride + x] = luminocity(source_data[y*stride + x]);

#if 0
	here is how splash deals with the transferfunction we should deal with this
	  at some point
	if (transferFunc) {
	  transferFunc->transform(&lum, &lum2);
	} else {
	  lum2 = lum;
	}
	p[x] = (int)(lum2 * 255.0 + 0.5);
#endif

      }
    }

    /* setup the new mask pattern */
    mask = cairo_pattern_create_for_surface(source);
    cairo_matrix_t patMatrix;
    cairo_pattern_get_matrix(group, &patMatrix);
    cairo_pattern_set_matrix(mask, &patMatrix);

    cairo_surface_destroy(source);
    cairo_surface_destroy(pats);
  } else {
    cairo_pattern_reference(group);
    mask = group;
  }
}

void CairoOutputDev::clearSoftMask(GfxState * /*state*/) {
  //XXX: should we be doing anything here?
}
Kristian Høgsberg's avatar
Kristian Høgsberg committed
658 659 660 661

void CairoOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str,
				    int width, int height, GBool invert,
				    GBool inlineImg) {
662 663
  unsigned char *buffer;
  unsigned char *dest;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
664
  cairo_surface_t *image;
665
  cairo_pattern_t *pattern;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
666 667
  int x, y;
  ImageStream *imgStr;
668
  Guchar *pix;
669
  cairo_matrix_t matrix;
670
  int invert_bit;
671
  int row_stride;
672

673 674 675 676
  /* FIXME: Doesn't the image mask support any colorspace? */
  cairo_set_source (cairo, fill_pattern);

  /* work around a cairo bug when scaling 1x1 surfaces */
677 678
  if (width == 1 && height == 1) {
    cairo_save (cairo);
679
    cairo_rectangle (cairo, 0., 0., width, height);
680 681 682 683 684
    cairo_fill (cairo);
    cairo_restore (cairo);
    return;
  }

685 686
  row_stride = (width + 3) & ~3;
  buffer = (unsigned char *) malloc (height * row_stride);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
687 688 689 690 691 692
  if (buffer == NULL) {
    error(-1, "Unable to allocate memory for image.");
    return;
  }

  /* TODO: Do we want to cache these? */
693
  imgStr = new ImageStream(str, width, 1, 1);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
694 695
  imgStr->reset();

696 697
  invert_bit = invert ? 1 : 0;

Kristian Høgsberg's avatar
Kristian Høgsberg committed
698
  for (y = 0; y < height; y++) {
699
    pix = imgStr->getLine();
700
    dest = buffer + y * row_stride;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
701
    for (x = 0; x < width; x++) {
702 703

      if (pix[x] ^ invert_bit)
Kristian Høgsberg's avatar
Kristian Høgsberg committed
704
	*dest++ = 0;
705 706
      else
	*dest++ = 255;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
707 708 709
    }
  }

710
  image = cairo_image_surface_create_for_data (buffer, CAIRO_FORMAT_A8,
711
					       width, height, row_stride);
712 713
  if (image == NULL) {
    delete imgStr;
714
    return;
715
  }
716
  pattern = cairo_pattern_create_for_surface (image);
717 718
  if (pattern == NULL) {
    delete imgStr;
719
    return;
720
  }
Kristian Høgsberg's avatar
Kristian Høgsberg committed
721

722 723 724
  cairo_matrix_init_translate (&matrix, 0, height);
  cairo_matrix_scale (&matrix, width, -height);

725
  cairo_pattern_set_matrix (pattern, &matrix);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
726

727 728 729
  /* we should actually be using CAIRO_FILTER_NEAREST here. However,
   * cairo doesn't yet do minifaction filtering causing scaled down
   * images with CAIRO_FILTER_NEAREST to look really bad */
730
  cairo_pattern_set_filter (pattern, CAIRO_FILTER_BEST);
731

732
  cairo_mask (cairo, pattern);
733

734
  cairo_pattern_destroy (pattern);
735 736
  cairo_surface_destroy (image);
  free (buffer);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
737 738 739
  delete imgStr;
}

740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812
void CairoOutputDev::drawMaskedImage(GfxState *state, Object *ref,
				Stream *str, int width, int height,
				GfxImageColorMap *colorMap,
				Stream *maskStr, int maskWidth,
				int maskHeight, GBool maskInvert)
{
  ImageStream *maskImgStr;
  maskImgStr = new ImageStream(maskStr, maskWidth, 1, 1);
  maskImgStr->reset();

  int row_stride = (maskWidth + 3) & ~3;
  unsigned char *maskBuffer;
  maskBuffer = (unsigned char *)gmalloc (row_stride * maskHeight);
  unsigned char *maskDest;
  cairo_surface_t *maskImage;
  cairo_pattern_t *maskPattern;
  Guchar *pix;
  int x, y;

  int invert_bit;
  
  invert_bit = maskInvert ? 1 : 0;

  for (y = 0; y < height; y++) {
    pix = maskImgStr->getLine();
    maskDest = maskBuffer + y * row_stride;
    for (x = 0; x < width; x++) {
      if (pix[x] ^ invert_bit)
	*maskDest++ = 0;
      else
	*maskDest++ = 255;
    }
  }

  maskImage = cairo_image_surface_create_for_data (maskBuffer, CAIRO_FORMAT_A8,
						 maskWidth, maskHeight, row_stride);

  delete maskImgStr;
  maskStr->close();

  unsigned char *buffer;
  unsigned int *dest;
  cairo_surface_t *image;
  cairo_pattern_t *pattern;
  ImageStream *imgStr;
  GfxRGB rgb;
  int alpha, i;
  cairo_matrix_t matrix;
  int is_identity_transform;

  buffer = (unsigned char *)gmalloc (width * height * 4);

  /* TODO: Do we want to cache these? */
  imgStr = new ImageStream(str, width,
			   colorMap->getNumPixelComps(),
			   colorMap->getBits());
  imgStr->reset();
  
  /* ICCBased color space doesn't do any color correction
   * so check its underlying color space as well */
  is_identity_transform = colorMap->getColorSpace()->getMode() == csDeviceRGB ||
		  colorMap->getColorSpace()->getMode() == csICCBased && 
		  ((GfxICCBasedColorSpace*)colorMap->getColorSpace())->getAlt()->getMode() == csDeviceRGB;

  for (y = 0; y < height; y++) {
    dest = (unsigned int *) (buffer + y * 4 * width);
    pix = imgStr->getLine();
    colorMap->getRGBLine (pix, dest, width);
  }

  image = cairo_image_surface_create_for_data (buffer, CAIRO_FORMAT_RGB24,
						 width, height, width * 4);

813 814
  if (image == NULL) {
    delete imgStr;
815
    return;
816
  }
817 818
  pattern = cairo_pattern_create_for_surface (image);
  maskPattern = cairo_pattern_create_for_surface (maskImage);
819 820
  if (pattern == NULL) {
    delete imgStr;
821
    return;
822
  }
823

824 825 826 827 828
  LOG (printf ("drawMaskedImage %dx%d\n", width, height));

  cairo_matrix_init_translate (&matrix, 0, height);
  cairo_matrix_scale (&matrix, width, -height);

829
  /* scale the mask to the size of the image unlike softMask */
830
  cairo_pattern_set_matrix (pattern, &matrix);
831
  cairo_pattern_set_matrix (maskPattern, &matrix);
832 833 834 835 836 837 838 839 840 841 842 843 844 845

  cairo_pattern_set_filter (pattern, CAIRO_FILTER_BILINEAR);
  cairo_set_source (cairo, pattern);
  cairo_mask (cairo, maskPattern);

  cairo_pattern_destroy (maskPattern);
  cairo_surface_destroy (maskImage);
  cairo_pattern_destroy (pattern);
  cairo_surface_destroy (image);
  free (buffer);
  free (maskBuffer);
  delete imgStr;
}

846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886
void CairoOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str,
				int width, int height,
				GfxImageColorMap *colorMap,
				Stream *maskStr,
				int maskWidth, int maskHeight,
				GfxImageColorMap *maskColorMap)
{
  ImageStream *maskImgStr;
  maskImgStr = new ImageStream(maskStr, maskWidth,
				       maskColorMap->getNumPixelComps(),
				       maskColorMap->getBits());
  maskImgStr->reset();

  int row_stride = (maskWidth + 3) & ~3;
  unsigned char *maskBuffer;
  maskBuffer = (unsigned char *)gmalloc (row_stride * maskHeight);
  unsigned char *maskDest;
  cairo_surface_t *maskImage;
  cairo_pattern_t *maskPattern;
  Guchar *pix;
  int x, y;
  for (y = 0; y < maskHeight; y++) {
    maskDest = (unsigned char *) (maskBuffer + y * row_stride);
    pix = maskImgStr->getLine();
    maskColorMap->getGrayLine (pix, maskDest, maskWidth);
  }

  maskImage = cairo_image_surface_create_for_data (maskBuffer, CAIRO_FORMAT_A8,
						 maskWidth, maskHeight, row_stride);

  delete maskImgStr;
  maskStr->close();

  unsigned char *buffer;
  unsigned int *dest;
  cairo_surface_t *image;
  cairo_pattern_t *pattern;
  ImageStream *imgStr;
  GfxRGB rgb;
  int alpha, i;
  cairo_matrix_t matrix;
887
  cairo_matrix_t maskMatrix;
888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912
  int is_identity_transform;

  buffer = (unsigned char *)gmalloc (width * height * 4);

  /* TODO: Do we want to cache these? */
  imgStr = new ImageStream(str, width,
			   colorMap->getNumPixelComps(),
			   colorMap->getBits());
  imgStr->reset();
  
  /* ICCBased color space doesn't do any color correction
   * so check its underlying color space as well */
  is_identity_transform = colorMap->getColorSpace()->getMode() == csDeviceRGB ||
		  colorMap->getColorSpace()->getMode() == csICCBased && 
		  ((GfxICCBasedColorSpace*)colorMap->getColorSpace())->getAlt()->getMode() == csDeviceRGB;

  for (y = 0; y < height; y++) {
    dest = (unsigned int *) (buffer + y * 4 * width);
    pix = imgStr->getLine();
    colorMap->getRGBLine (pix, dest, width);
  }

  image = cairo_image_surface_create_for_data (buffer, CAIRO_FORMAT_RGB24,
						 width, height, width * 4);

913 914
  if (image == NULL) {
    delete imgStr;
915
    return;
916
  }
917 918
  pattern = cairo_pattern_create_for_surface (image);
  maskPattern = cairo_pattern_create_for_surface (maskImage);
919 920
  if (pattern == NULL) {
    delete imgStr;
921
    return;
922
  }
923

924 925 926 927 928 929 930
  LOG (printf ("drawSoftMaskedImage %dx%d\n", width, height));

  cairo_matrix_init_translate (&matrix, 0, height);
  cairo_matrix_scale (&matrix, width, -height);

  cairo_matrix_init_translate (&maskMatrix, 0, maskHeight);
  cairo_matrix_scale (&maskMatrix, maskWidth, -maskHeight);
931 932

  cairo_pattern_set_matrix (pattern, &matrix);
933
  cairo_pattern_set_matrix (maskPattern, &maskMatrix);
934 935 936 937 938 939 940 941 942 943 944 945 946

  cairo_pattern_set_filter (pattern, CAIRO_FILTER_BILINEAR);
  cairo_set_source (cairo, pattern);
  cairo_mask (cairo, maskPattern);

  cairo_pattern_destroy (maskPattern);
  cairo_surface_destroy (maskImage);
  cairo_pattern_destroy (pattern);
  cairo_surface_destroy (image);
  free (buffer);
  free (maskBuffer);
  delete imgStr;
}
Kristian Høgsberg's avatar
Kristian Høgsberg committed
947 948 949
void CairoOutputDev::drawImage(GfxState *state, Object *ref, Stream *str,
				int width, int height,
				GfxImageColorMap *colorMap,
950 951
				int *maskColors, GBool inlineImg)
{
952
  unsigned char *buffer;
953
  unsigned int *dest;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
954
  cairo_surface_t *image;
955
  cairo_pattern_t *pattern;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
956 957 958 959 960
  int x, y;
  ImageStream *imgStr;
  Guchar *pix;
  GfxRGB rgb;
  int alpha, i;
961
  cairo_matrix_t matrix;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
962 963
  int is_identity_transform;
  
964
  buffer = (unsigned char *)gmalloc (width * height * 4);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
965 966 967 968 969 970 971 972 973 974 975 976

  /* TODO: Do we want to cache these? */
  imgStr = new ImageStream(str, width,
			   colorMap->getNumPixelComps(),
			   colorMap->getBits());
  imgStr->reset();
  
  /* ICCBased color space doesn't do any color correction
   * so check its underlying color space as well */
  is_identity_transform = colorMap->getColorSpace()->getMode() == csDeviceRGB ||
		  colorMap->getColorSpace()->getMode() == csICCBased && 
		  ((GfxICCBasedColorSpace*)colorMap->getColorSpace())->getAlt()->getMode() == csDeviceRGB;
977 978 979 980 981 982 983 984

  if (maskColors) {
    for (y = 0; y < height; y++) {
      dest = (unsigned int *) (buffer + y * 4 * width);
      pix = imgStr->getLine();
      colorMap->getRGBLine (pix, dest, width);

      for (x = 0; x < width; x++) {
Kristian Høgsberg's avatar
Kristian Høgsberg committed
985
	for (i = 0; i < colorMap->getNumPixelComps(); ++i) {
986 987 988 989
	  
	  if (pix[i] < maskColors[2*i] * 255||
	      pix[i] > maskColors[2*i+1] * 255) {
	    *dest = *dest | 0xff000000;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
990 991 992
	    break;
	  }
	}
993 994
	pix += colorMap->getNumPixelComps();
	dest++;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
995 996
      }
    }
997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009

    image = cairo_image_surface_create_for_data (buffer, CAIRO_FORMAT_ARGB32,
						 width, height, width * 4);
  }
  else {
    for (y = 0; y < height; y++) {
      dest = (unsigned int *) (buffer + y * 4 * width);
      pix = imgStr->getLine();
      colorMap->getRGBLine (pix, dest, width);
    }

    image = cairo_image_surface_create_for_data (buffer, CAIRO_FORMAT_RGB24,
						 width, height, width * 4);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
1010 1011
  }

1012 1013 1014 1015
  if (image == NULL) {
   delete imgStr;
   return;
  }
1016
  pattern = cairo_pattern_create_for_surface (image);
1017 1018
  if (pattern == NULL) {
    delete imgStr;
1019
    return;
1020
  }
Kristian Høgsberg's avatar
Kristian Høgsberg committed
1021

1022 1023 1024 1025 1026
  LOG (printf ("drawImageMask %dx%d\n", width, height));
  
  cairo_matrix_init_translate (&matrix, 0, height);
  cairo_matrix_scale (&matrix, width, -height);

1027 1028
  cairo_pattern_set_matrix (pattern, &matrix);

1029
  cairo_pattern_set_filter (pattern, CAIRO_FILTER_BILINEAR);
1030 1031
  cairo_set_source (cairo, pattern);
  cairo_paint (cairo);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
1032

1033
  cairo_pattern_destroy (pattern);
1034 1035
  cairo_surface_destroy (image);
  free (buffer);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
1036 1037
  delete imgStr;
}
1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235


//------------------------------------------------------------------------
// ImageOutputDev
//------------------------------------------------------------------------

CairoImageOutputDev::CairoImageOutputDev()
{
  images = NULL;
  numImages = 0;
  size = 0;
}

CairoImageOutputDev::~CairoImageOutputDev()
{
  int i;

  for (i = 0; i < numImages; i++)
    delete images[i];
  gfree (images);
}

void CairoImageOutputDev::saveImage(CairoImage *image)
{ 
  if (numImages >= size) {
	  size += 16;
	  images = (CairoImage **) greallocn (images, size, sizeof (CairoImage *));
  }
  images[numImages++] = image;
}	

void CairoImageOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str,
					int width, int height, GBool invert,
					GBool inlineImg)
{
  cairo_t *cr;
  cairo_surface_t *surface;
  double x1, y1, x2, y2;
  double *ctm;
  double mat[6];
  CairoImage *image;

  ctm = state->getCTM();
  
  mat[0] = ctm[0];
  mat[1] = ctm[1];
  mat[2] = -ctm[2];
  mat[3] = -ctm[3];
  mat[4] = ctm[2] + ctm[4];
  mat[5] = ctm[3] + ctm[5];
  x1 = mat[4];
  y1 = mat[5];
  x2 = x1 + width;
  y2 = y1 + height;

  surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
  cr = cairo_create (surface);
  setCairo (cr);
  cairo_translate (cr, 0, height);
  cairo_scale (cr, width, -height);

  CairoOutputDev::drawImageMask(state, ref, str, width, height, invert, inlineImg);

  image = new CairoImage (surface, x1, y1, x2, y2);
  saveImage (image);
  
  setCairo (NULL);
  cairo_surface_destroy (surface);
  cairo_destroy (cr);
}

void CairoImageOutputDev::drawImage(GfxState *state, Object *ref, Stream *str,
				    int width, int height, GfxImageColorMap *colorMap,
				    int *maskColors, GBool inlineImg)
{
  cairo_t *cr;
  cairo_surface_t *surface;
  double x1, y1, x2, y2;
  double *ctm;
  double mat[6];
  CairoImage *image;

  ctm = state->getCTM();
  
  mat[0] = ctm[0];
  mat[1] = ctm[1];
  mat[2] = -ctm[2];
  mat[3] = -ctm[3];
  mat[4] = ctm[2] + ctm[4];
  mat[5] = ctm[3] + ctm[5];
  x1 = mat[4];
  y1 = mat[5];
  x2 = x1 + width;
  y2 = y1 + height;

  surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
  cr = cairo_create (surface);
  setCairo (cr);
  cairo_translate (cr, 0, height);
  cairo_scale (cr, width, -height);

  CairoOutputDev::drawImage(state, ref, str, width, height, colorMap, maskColors, inlineImg);

  image = new CairoImage (surface, x1, y1, x2, y2);
  saveImage (image);
  
  setCairo (NULL);
  cairo_surface_destroy (surface);
  cairo_destroy (cr);
}

void CairoImageOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str,
					      int width, int height,
					      GfxImageColorMap *colorMap,
					      Stream *maskStr,
					      int maskWidth, int maskHeight,
					      GfxImageColorMap *maskColorMap)
{
  cairo_t *cr;
  cairo_surface_t *surface;
  double x1, y1, x2, y2;
  double *ctm;
  double mat[6];
  CairoImage *image;

  ctm = state->getCTM();
  
  mat[0] = ctm[0];
  mat[1] = ctm[1];
  mat[2] = -ctm[2];
  mat[3] = -ctm[3];
  mat[4] = ctm[2] + ctm[4];
  mat[5] = ctm[3] + ctm[5];
  x1 = mat[4];
  y1 = mat[5];
  x2 = x1 + width;
  y2 = y1 + height;

  surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
  cr = cairo_create (surface);
  setCairo (cr);
  cairo_translate (cr, 0, height);
  cairo_scale (cr, width, -height);

  CairoOutputDev::drawSoftMaskedImage(state, ref, str, width, height, colorMap,
				      maskStr, maskWidth, maskHeight, maskColorMap);

  image = new CairoImage (surface, x1, y1, x2, y2);
  saveImage (image);
  
  setCairo (NULL);
  cairo_surface_destroy (surface);
  cairo_destroy (cr);
}

void CairoImageOutputDev::drawMaskedImage(GfxState *state, Object *ref, Stream *str,
					  int width, int height,
					  GfxImageColorMap *colorMap,
					  Stream *maskStr,
					  int maskWidth, int maskHeight,
					  GBool maskInvert)
{
  cairo_t *cr;
  cairo_surface_t *surface;
  double x1, y1, x2, y2;
  double *ctm;
  double mat[6];
  CairoImage *image;

  ctm = state->getCTM();
  
  mat[0] = ctm[0];
  mat[1] = ctm[1];
  mat[2] = -ctm[2];
  mat[3] = -ctm[3];
  mat[4] = ctm[2] + ctm[4];
  mat[5] = ctm[3] + ctm[5];
  x1 = mat[4];
  y1 = mat[5];
  x2 = x1 + width;
  y2 = y1 + height;

  surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
  cr = cairo_create (surface);
  setCairo (cr);
  cairo_translate (cr, 0, height);
  cairo_scale (cr, width, -height);

  CairoOutputDev::drawMaskedImage(state, ref, str, width, height, colorMap,
				  maskStr, maskWidth, maskHeight, maskInvert);

  image = new CairoImage (surface, x1, y1, x2, y2);
  saveImage (image);
  
  setCairo (NULL);
  cairo_surface_destroy (surface);
  cairo_destroy (cr);
}