Quellcode durchsuchen

PNMPainter, PNMBrush

David Rose vor 19 Jahren
Ursprung
Commit
7732928c0e

+ 15 - 5
panda/src/pnmimage/Sources.pp

@@ -12,23 +12,33 @@
   #define SOURCES \
      config_pnmimage.h \
      pnmbitio.h \
+     pnmBrush.h pnmBrush.I \
      pnmFileType.h pnmFileTypeRegistry.h pnmImage.I  \
-     pnmImage.h pnmImageHeader.I pnmImageHeader.h pnmReader.I  \
+     pnmImage.h pnmImageHeader.I pnmImageHeader.h  \
+     pnmPainter.h pnmPainter.I \
+     pnmReader.I \
      pnmReader.h pnmWriter.I pnmWriter.h pnmimage_base.h \
      ppmcmap.h
 
   #define INCLUDED_SOURCES \
      config_pnmimage.cxx \
+     pnm-image-filter.cxx \
      pnmbitio.cxx \
-     pnm-image-filter.cxx pnmFileType.cxx  \
+     pnmBrush.cxx \
+     pnmFileType.cxx  \
      pnmFileTypeRegistry.cxx pnmImage.cxx pnmImageHeader.cxx  \
+     pnmPainter.cxx \
      pnmReader.cxx pnmWriter.cxx pnmimage_base.cxx \
      ppmcmap.cxx
 
   #define INSTALL_HEADERS \
-    config_pnmimage.h pnmFileType.h pnmFileTypeRegistry.h pnmImage.I \
-    pnmImage.h pnmImageHeader.I pnmImageHeader.h pnmReader.I \
-    pnmReader.h pnmWriter.I pnmWriter.h pnmimage_base.h
+     config_pnmimage.h \
+     pnmBrush.h pnmBrush.I \
+     pnmFileType.h pnmFileTypeRegistry.h pnmImage.I \
+     pnmImage.h pnmImageHeader.I pnmImageHeader.h \
+     pnmPainter.h pnmPainter.I \
+     pnmReader.I \
+     pnmReader.h pnmWriter.I pnmWriter.h pnmimage_base.h
 
   #define IGATESCAN all
 

+ 55 - 0
panda/src/pnmimage/pnmBrush.I

@@ -0,0 +1,55 @@
+// Filename: pnmBrush.I
+// Created by:  drose (01Feb07)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMBrush::Constructor
+//       Access: Protected
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE PNMBrush::
+PNMBrush(double xc, double yc) : _xc(xc), _yc(yc) {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMBrush::get_xc
+//       Access: Protected
+//  Description: Returns the coordinates of the brush's center
+//               pixel.  For a one-pixel brush, this will be (0.5,
+//               0.5); for a centered two-pixel brush, this will be
+//               (1.0, 1.0); for a centered three-pixel brush, this
+//               will be (1.5, 1.5); and so on.
+////////////////////////////////////////////////////////////////////
+INLINE double PNMBrush::
+get_xc() const {
+  return _xc;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMBrush::get_xc
+//       Access: Protected
+//  Description: Returns the coordinates of the brush's center
+//               pixel.  For a one-pixel brush, this will be (0.5,
+//               0.5); for a centered two-pixel brush, this will be
+//               (1.0, 1.0); for a centered three-pixel brush, this
+//               will be (1.5, 1.5); and so on.
+////////////////////////////////////////////////////////////////////
+INLINE double PNMBrush::
+get_yc() const {
+  return _yc;
+}

+ 422 - 0
panda/src/pnmimage/pnmBrush.cxx

@@ -0,0 +1,422 @@
+// Filename: pnmBrush.cxx
+// Created by:  drose (01Feb07)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "pnmBrush.h"
+#include "config_pnmimage.h"
+#include "cmath.h"
+
+// A PNMTransparentBrush doesn't draw or fill anything.
+class EXPCL_PANDA PNMTransparentBrush : public PNMBrush {
+public:
+  PNMTransparentBrush() : 
+    PNMBrush(0.0, 0.0) { }
+
+  virtual void draw(PNMImage &, int, int, double) {
+  }
+
+  virtual void fill(PNMImage &, int, int, int, int, int) {
+  }
+};
+
+// A PNMPixelBrush is a family of brushes that draw one pixel at a time.
+class EXPCL_PANDA PNMPixelBrush : public PNMBrush {
+protected:
+  PNMPixelBrush(const Colord &color) : 
+    PNMBrush(0.5, 0.5), _rgb(color[0], color[1], color[2]), _a(color[3]) { }
+
+  RGBColord _rgb;
+  double _a;
+};
+
+// Arbitrarily sets the pixel to a particular color, with no antialiasing.
+class EXPCL_PANDA PNMSetPixelBrush : public PNMPixelBrush {
+public:
+  PNMSetPixelBrush(const Colord &color) : PNMPixelBrush(color) { }
+
+  virtual void draw(PNMImage &image, int x, int y, double pixel_scale) {
+    if (x >= 0 && x < image.get_x_size() && 
+        y >= 0 && y < image.get_y_size() &&
+        pixel_scale >= 0.5) {
+      image.set_xel(x, y, _rgb);
+      if (image.has_alpha()) {
+        image.set_alpha(x, y, _a);
+      }
+    }
+  }
+
+  virtual void fill(PNMImage &image, int xfrom, int xto, int y,
+                    int xo, int yo) {
+    if (y >= 0 && y < image.get_y_size()) {
+      xfrom = max(xfrom, 0);
+      xto = min(xto, image.get_x_size() - 1);
+      for (int x = xfrom; x <= xto; ++x) {
+        image.set_xel(x, y, _rgb);
+      }
+      if (image.has_alpha()) {
+        for (int x = xfrom; x <= xto; ++x) {
+          image.set_alpha(x, y, _a);
+        }
+      }
+    }
+  }
+};
+
+// Blends the pixel in to the existing background.
+class EXPCL_PANDA PNMBlendPixelBrush : public PNMPixelBrush {
+public:
+  PNMBlendPixelBrush(const Colord &color) : PNMPixelBrush(color) { }
+
+  virtual void draw(PNMImage &image, int x, int y, double pixel_scale) {
+    if (x >= 0 && x < image.get_x_size() && 
+        y >= 0 && y < image.get_y_size()) {
+      image.blend(x, y, _rgb, _a * pixel_scale);
+    }
+  }
+
+  virtual void fill(PNMImage &image, int xfrom, int xto, int y,
+                    int xo, int yo) {
+    if (y >= 0 && y < image.get_y_size()) {
+      xfrom = max(xfrom, 0);
+      xto = min(xto, image.get_x_size() - 1);
+      for (int x = xfrom; x <= xto; ++x) {
+        image.blend(x, y, _rgb, _a);
+      }
+    }
+  }
+};
+
+// Darkens the pixel in the existing background.
+class EXPCL_PANDA PNMDarkenPixelBrush : public PNMPixelBrush {
+public:
+  PNMDarkenPixelBrush(const Colord &color) : PNMPixelBrush(color) { }
+
+  virtual void draw(PNMImage &image, int x, int y, double pixel_scale) {
+    if (x >= 0 && x < image.get_x_size() && 
+        y >= 0 && y < image.get_y_size()) {
+      RGBColord rgb = image.get_xel(x, y);
+      RGBColord p;
+      p.set(min(1.0 - (1.0 - _rgb[0]) * pixel_scale, rgb[0]), 
+            min(1.0 - (1.0 - _rgb[1]) * pixel_scale, rgb[1]), 
+            min(1.0 - (1.0 - _rgb[2]) * pixel_scale, rgb[2]));
+      image.set_xel(x, y, p);
+
+      if (image.has_alpha()) {
+        double a = image.get_alpha(x, y);
+        image.set_alpha(x, y, min(1.0 - (1.0 - _a) * pixel_scale, a));
+      }
+    }
+  }
+
+  virtual void fill(PNMImage &image, int xfrom, int xto, int y,
+                    int xo, int yo) {
+    if (y >= 0 && y < image.get_y_size()) {
+      xfrom = max(xfrom, 0);
+      xto = min(xto, image.get_x_size() - 1);
+      for (int x = xfrom; x <= xto; ++x) {
+        RGBColord rgb = image.get_xel(x, y);
+        RGBColord p;
+        p.set(min(_rgb[0], rgb[0]), 
+              min(_rgb[1], rgb[1]), 
+              min(_rgb[2], rgb[2]));
+        image.set_xel(x, y, p);
+      }
+      if (image.has_alpha()) {
+        for (int x = xfrom; x <= xto; ++x) {
+          double a = image.get_alpha(x, y);
+          image.set_alpha(x, y, min(_a, a));
+        }
+      }
+    }
+  }
+};
+
+// Lightens the pixel in the existing background.
+class EXPCL_PANDA PNMLightenPixelBrush : public PNMPixelBrush {
+public:
+  PNMLightenPixelBrush(const Colord &color) : PNMPixelBrush(color) { }
+
+  virtual void draw(PNMImage &image, int x, int y, double pixel_scale) {
+    if (x >= 0 && x < image.get_x_size() && 
+        y >= 0 && y < image.get_y_size()) {
+      RGBColord rgb = image.get_xel(x, y);
+      RGBColord p;
+      p.set(max(_rgb[0] * pixel_scale, rgb[0]), 
+            max(_rgb[1] * pixel_scale, rgb[1]), 
+            max(_rgb[2] * pixel_scale, rgb[2]));
+      image.set_xel(x, y, p);
+
+      if (image.has_alpha()) {
+        double a = image.get_alpha(x, y);
+        image.set_alpha(x, y, max(_a * pixel_scale, a));
+      }
+    }
+  }
+
+  virtual void fill(PNMImage &image, int xfrom, int xto, int y,
+                    int xo, int yo) {
+    if (y >= 0 && y < image.get_y_size()) {
+      xfrom = max(xfrom, 0);
+      xto = max(xto, image.get_x_size() - 1);
+      for (int x = xfrom; x <= xto; ++x) {
+        RGBColord rgb = image.get_xel(x, y);
+        RGBColord p;
+        p.set(max(_rgb[0], rgb[0]), 
+              max(_rgb[1], rgb[1]), 
+              max(_rgb[2], rgb[2]));
+        image.set_xel(x, y, p);
+      }
+      if (image.has_alpha()) {
+        for (int x = xfrom; x <= xto; ++x) {
+          double a = image.get_alpha(x, y);
+          image.set_alpha(x, y, max(_a, a));
+        }
+      }
+    }
+  }
+};
+
+// A PNMImageBrush is a family of brushes that draw an image at a time.
+class EXPCL_PANDA PNMImageBrush : public PNMBrush {
+protected:
+  PNMImageBrush(const PNMImage &image, double xc, double yc) : 
+    PNMBrush(xc, yc),
+    _image(image) 
+  {
+  }
+
+  virtual void fill(PNMImage &image, int xfrom, int xto, int y,
+                    int xo, int yo) {
+    if (y >= 0 && y < image.get_y_size()) {
+      xfrom = max(xfrom, 0);
+      xto = min(xto, image.get_x_size() - 1);
+
+      int x_pat = (xfrom + xo) % _image.get_x_size();
+      int y_pat = (y + yo) % _image.get_y_size();
+
+      // Copy the first (right half) of the image scanline.
+      int x = xfrom;
+      do_scanline(image, x, y, x_pat, y_pat, xto - x + 1, 1);
+      // Now repeatedly make more copies of the image scanline.
+      x += _image.get_x_size() - x_pat;
+      while (x <= xto) {
+        do_scanline(image, x, y, 0, y_pat, xto - x + 1, 1);
+        x += _image.get_x_size();
+      }
+    }
+  }
+
+  virtual void do_scanline(PNMImage &image, int xto, int yto,
+                           int xfrom, int yfrom, int x_size, int y_size)=0;
+
+  PNMImage _image;
+};
+
+// Sets the pixels from the rectangular image, with no antialiasing.
+class EXPCL_PANDA PNMSetImageBrush : public PNMImageBrush {
+public:
+  PNMSetImageBrush(const PNMImage &image, double xc, double yc) : 
+    PNMImageBrush(image, xc, yc) { }
+
+  virtual void draw(PNMImage &image, int x, int y, double pixel_scale) {
+    if (pixel_scale >= 0.5) {
+      image.copy_sub_image(_image, x, y);
+    }
+  }
+
+  virtual void do_scanline(PNMImage &image, int xto, int yto, 
+                           int xfrom, int yfrom, int x_size, int y_size) {
+    image.copy_sub_image(_image, xto, yto, xfrom, yfrom, x_size, y_size);
+  }
+};
+
+// Blends the pixels in using alpha.
+class EXPCL_PANDA PNMBlendImageBrush : public PNMImageBrush {
+public:
+  PNMBlendImageBrush(const PNMImage &image, double xc, double yc) : 
+    PNMImageBrush(image, xc, yc) { }
+
+  virtual void draw(PNMImage &image, int x, int y, double pixel_scale) {
+    image.blend_sub_image(_image, x, y, 0, 0, -1, -1, pixel_scale);
+  }
+
+  virtual void do_scanline(PNMImage &image, int xto, int yto, 
+                           int xfrom, int yfrom, int x_size, int y_size) {
+    image.blend_sub_image(_image, xto, yto, xfrom, yfrom, x_size, y_size);
+  }
+};
+
+// Darkens the pixels
+class EXPCL_PANDA PNMDarkenImageBrush : public PNMImageBrush {
+public:
+  PNMDarkenImageBrush(const PNMImage &image, double xc, double yc) : 
+    PNMImageBrush(image, xc, yc) { }
+
+  virtual void draw(PNMImage &image, int x, int y, double pixel_scale) {
+    image.darken_sub_image(_image, x, y, 0, 0, -1, -1, pixel_scale);
+  }
+
+  virtual void do_scanline(PNMImage &image, int xto, int yto, 
+                           int xfrom, int yfrom, int x_size, int y_size) {
+    image.darken_sub_image(_image, xto, yto, xfrom, yfrom, x_size, y_size);
+  }
+};
+
+// Lightens the pixels
+class EXPCL_PANDA PNMLightenImageBrush : public PNMImageBrush {
+public:
+  PNMLightenImageBrush(const PNMImage &image, double xc, double yc) : 
+    PNMImageBrush(image, xc, yc) { }
+
+  virtual void draw(PNMImage &image, int x, int y, double pixel_scale) {
+    image.lighten_sub_image(_image, x, y, 0, 0, -1, -1, pixel_scale);
+  }
+
+  virtual void do_scanline(PNMImage &image, int xto, int yto, 
+                           int xfrom, int yfrom, int x_size, int y_size) {
+    image.lighten_sub_image(_image, xto, yto, xfrom, yfrom, x_size, y_size);
+  }
+};
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMBrush::Destructor
+//       Access: Published, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+PNMBrush::
+~PNMBrush() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMBrush::make_transparent
+//       Access: Published, Static
+//  Description: Returns a new brush that does not paint anything.
+//               Can be used as either a pen or a fill brush to make
+//               borderless or unfilled shapes, respectively.
+////////////////////////////////////////////////////////////////////
+PT(PNMBrush) PNMBrush::
+make_transparent() {
+  return new PNMTransparentBrush();
+}
+  
+////////////////////////////////////////////////////////////////////
+//     Function: PNMBrush::make_pixel
+//       Access: Published, Static
+//  Description: Returns a new brush that paints a single pixel of the
+//               indicated color on a border, or paints a solid color
+//               in an interior.
+////////////////////////////////////////////////////////////////////
+PT(PNMBrush) PNMBrush::
+make_pixel(const Colord &color, PNMBrush::BrushEffect effect) {
+  switch (effect) {
+  case BE_set:
+    return new PNMSetPixelBrush(color);
+
+  case BE_blend:
+    return new PNMBlendPixelBrush(color);
+
+  case BE_darken:
+    return new PNMDarkenPixelBrush(color);
+
+  case BE_lighten:
+    return new PNMLightenPixelBrush(color);
+  }
+
+  pnmimage_cat.error()
+    << "**Invalid BrushEffect (" << (int)effect << ")**\n";
+  return new PNMSetPixelBrush(color);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMBrush::make_spot
+//       Access: Published, Static
+//  Description: Returns a new brush that paints a spot of the
+//               indicated color and radius.  If fuzzy is true, the
+//               spot is fuzzy; otherwise, it is hard-edged.
+////////////////////////////////////////////////////////////////////
+PT(PNMBrush) PNMBrush::
+make_spot(const Colord &color, double radius, bool fuzzy,
+          BrushEffect effect) {
+  Colord bg;
+
+  switch (effect) {
+  case BE_set:
+    bg.set(0, 0, 0, 0);
+    break;
+
+  case BE_blend:
+    bg.set(color[0], color[1], color[2], 0.0);
+    break;
+
+  case BE_darken:
+    bg.set(1, 1, 1, 1);
+    break;
+
+  case BE_lighten:
+    bg.set(0, 0, 0, 0);
+    break;
+
+  default:
+    pnmimage_cat.error()
+      << "**Invalid BrushEffect (" << (int)effect << ")**\n";
+  }
+
+  int size = (int)cceil(radius * 2.0);
+  double half_size = (double)size * 0.5;
+  PNMImage spot(size, size, 4);
+  double r = half_size / radius;
+
+  if (fuzzy) {
+    spot.render_spot(color, bg, 0.0, r);
+  } else {
+    spot.render_spot(color, bg, r, r);
+  }
+  return make_image(spot, half_size, half_size, effect);
+}
+  
+////////////////////////////////////////////////////////////////////
+//     Function: PNMBrush::make_image
+//       Access: Published, Static
+//  Description: Returns a new brush that paints with the indicated
+//               image.  xc and yc indicate the pixel in the center of
+//               the brush.
+//
+//               The brush makes a copy of the image; it is safe to
+//               deallocate or modify the image after making this
+//               call.
+////////////////////////////////////////////////////////////////////
+PT(PNMBrush) PNMBrush::
+make_image(const PNMImage &image, double xc, double yc,
+           PNMBrush::BrushEffect effect) {
+  switch (effect) {
+  case BE_set:
+    return new PNMSetImageBrush(image, xc, yc);
+
+  case BE_blend:
+    return new PNMBlendImageBrush(image, xc, yc);
+
+  case BE_darken:
+    return new PNMDarkenImageBrush(image, xc, yc);
+
+  case BE_lighten:
+    return new PNMLightenImageBrush(image, xc, yc);
+  }
+
+  pnmimage_cat.error()
+    << "**Invalid BrushEffect (" << (int)effect << ")**\n";
+  return new PNMSetImageBrush(image, xc, yc);
+}

+ 80 - 0
panda/src/pnmimage/pnmBrush.h

@@ -0,0 +1,80 @@
+// Filename: pnmBrush.h
+// Created by:  drose (01Feb07)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef PNMBRUSH_H
+#define PNMBRUSH_H
+
+#include "pandabase.h"
+#include "referenceCount.h"
+#include "pointerTo.h"
+#include "luse.h"
+
+class PNMImage;
+
+////////////////////////////////////////////////////////////////////
+//       Class : PNMBrush
+// Description : This class is used to control the shape and color of
+//               the drawing operations performed by a PNMPainter
+//               object.
+//
+//               Normally, you don't create a PNMBrush directly;
+//               instead, use one of the static PNMBrush::make_*()
+//               methods provided here.
+//
+//               A PNMBrush is used to draw the border of a polygon or
+//               rectangle, as well as for filling its interior.  When
+//               it is used to draw a border, the brush is "smeared"
+//               over the border; when it is used to fill the
+//               interior, it is tiled through the interior.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA PNMBrush : public ReferenceCount {
+protected:
+  INLINE PNMBrush(double xc, double yc);
+
+PUBLISHED:
+  virtual ~PNMBrush();
+
+  enum BrushEffect {
+    BE_set,
+    BE_blend,
+    BE_darken,
+    BE_lighten,
+  };
+
+  static PT(PNMBrush) make_transparent();
+  static PT(PNMBrush) make_pixel(const Colord &color, BrushEffect effect = BE_blend);
+  static PT(PNMBrush) make_spot(const Colord &color, double radius, bool fuzzy,
+                                BrushEffect effect = BE_blend);
+  static PT(PNMBrush) make_image(const PNMImage &image, double xc, double yc,
+                                 BrushEffect effect = BE_blend);
+
+public:
+  INLINE double get_xc() const;
+  INLINE double get_yc() const;
+
+  virtual void draw(PNMImage &image, int x, int y, double pixel_scale)=0;
+  virtual void fill(PNMImage &image, int xfrom, int xto, int y,
+                    int xo, int yo)=0;
+
+protected:
+  double _xc, _yc;
+};
+
+#include "pnmBrush.I"
+
+#endif

+ 74 - 0
panda/src/pnmimage/pnmImage.I

@@ -805,3 +805,77 @@ INLINE xelval *PNMImage::
 alpha_row(int y) const {
   return _alpha + y * _x_size;
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMImage::setup_sub_image
+//       Access: Private
+//  Description: Computes xmin, ymin, xmax, and ymax, based on the
+//               input parameters for copy_sub_image() and related
+//               methods.
+////////////////////////////////////////////////////////////////////
+INLINE void PNMImage::
+setup_sub_image(const PNMImage &copy, int &xto, int &yto,
+                int &xfrom, int &yfrom, int &x_size, int &y_size,
+                int &xmin, int &ymin, int &xmax, int &ymax) {
+  if (x_size < 0) {
+    x_size = copy.get_x_size() - xfrom;
+  }
+  if (y_size < 0) {
+    y_size = copy.get_y_size() - yfrom;
+  }
+
+  if (xfrom < 0) {
+    xto += -xfrom;
+    x_size -= -xfrom;
+    xfrom = 0;
+  }
+  if (yfrom < 0) {
+    yto += -yfrom;
+    y_size -= -yfrom;
+    yfrom = 0;
+  }
+
+  if (xto < 0) {
+    xfrom += -xto;
+    x_size -= -xto;
+    xto = 0;
+  }
+  if (yto < 0) {
+    yfrom += -yto;
+    y_size -= -yto;
+    yto = 0;
+  }
+
+  x_size = min(x_size, copy.get_x_size() - xfrom);
+  y_size = min(y_size, copy.get_y_size() - yfrom);
+
+  xmin = xto;
+  ymin = yto;
+
+  xmax = min(xmin + x_size, get_x_size());
+  ymax = min(ymin + y_size, get_y_size());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMImage::compute_spot_pixel
+//       Access: Private, Static
+//  Description: Called by render_spot to compute the color of a
+//               single pixel, based in (the square of) its distance
+//               from the center.
+////////////////////////////////////////////////////////////////////
+INLINE void PNMImage::
+compute_spot_pixel(Colord &c, double d2, 
+                   double min_radius, double max_radius,
+                   const Colord &fg, const Colord &bg) {
+  double d = sqrt(d2);
+  if (d > max_radius) {
+    c = bg;
+  } else if (d > min_radius) {
+    d = (d - min_radius) / (max_radius - min_radius);
+    double d2 = d * d;
+    double t = (3.0 * d2) - (2.0 * d * d2);
+    c = fg + t * (bg - fg);
+  } else {
+    c = fg;
+  }
+}

+ 278 - 69
panda/src/pnmimage/pnmImage.cxx

@@ -19,6 +19,7 @@
 #include "pnmImage.h"
 #include "pnmReader.h"
 #include "pnmWriter.h"
+#include "pnmBrush.h"
 #include "config_pnmimage.h"
 
 ////////////////////////////////////////////////////////////////////
@@ -100,10 +101,9 @@ clear(int x_size, int y_size, int num_channels,
 void PNMImage::
 copy_from(const PNMImage &copy) {
   clear();
+  copy_header_from(copy);
 
   if (copy.is_valid()) {
-    copy_header_from(copy);
-
     if (has_alpha()) {
       memcpy(_alpha, copy._alpha, sizeof(xelval) * _y_size * _x_size);
     }
@@ -132,6 +132,28 @@ copy_header_from(const PNMImageHeader &header) {
   setup_rc();
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PNMImage::take_from
+//       Access: Published
+//  Description: Move the contents of the other image into this one,
+//               and empty the other image.
+////////////////////////////////////////////////////////////////////
+void PNMImage::
+take_from(PNMImage &orig) {
+  clear();
+  PNMImageHeader::operator = (orig);
+  setup_rc();
+
+  if (has_alpha()) {
+    _alpha = orig._alpha;
+    orig._alpha = NULL;
+  }
+  _array = orig._array;
+  orig._array = NULL;
+
+  orig.clear();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PNMImage::fill_val
 //       Access: Published
@@ -581,39 +603,41 @@ blend(int x, int y, double r, double g, double b, double alpha) {
 void PNMImage::
 copy_sub_image(const PNMImage &copy, int xto, int yto,
                int xfrom, int yfrom, int x_size, int y_size) {
-  if (xfrom < 0) {
-    xto += -xfrom;
-    xfrom = 0;
-  }
-  if (yfrom < 0) {
-    yto += -yfrom;
-    yfrom = 0;
-  }
-
-  x_size = (x_size < 0) ?
-    copy.get_x_size() :
-    min(x_size, copy.get_x_size() - xfrom);
-  y_size = (y_size < 0) ?
-    copy.get_y_size() :
-    min(y_size, copy.get_y_size() - yfrom);
+  int xmin, ymin, xmax, ymax;
+  setup_sub_image(copy, xto, yto, xfrom, yfrom, x_size, y_size,
+                  xmin, ymin, xmax, ymax);
 
-  int xmin = max(0, xto);
-  int ymin = max(0, yto);
-
-  int xmax = min(xmin + x_size, get_x_size());
-  int ymax = min(ymin + y_size, get_y_size());
-
-  int x, y;
-  for (y = ymin; y < ymax; y++) {
-    for (x = xmin; x < xmax; x++) {
-      set_xel(x, y, copy.get_xel(x - xmin + xfrom, y - ymin + yfrom));
+  if (get_maxval() == copy.get_maxval()) {
+    // The simple case: no pixel value rescaling is required.
+    int x, y;
+    for (y = ymin; y < ymax; y++) {
+      for (x = xmin; x < xmax; x++) {
+        set_xel_val(x, y, copy.get_xel_val(x - xmin + xfrom, y - ymin + yfrom));
+      }
+    }
+    
+    if (has_alpha() && copy.has_alpha()) {
+      for (y = ymin; y < ymax; y++) {
+        for (x = xmin; x < xmax; x++) {
+          set_alpha_val(x, y, copy.get_alpha_val(x - xmin + xfrom, y - ymin + yfrom));
+        }
+      }
     }
-  }
 
-  if (has_alpha() && copy.has_alpha()) {
+  } else {
+    // The harder case: rescale pixel values according to maxval.
+    int x, y;
     for (y = ymin; y < ymax; y++) {
       for (x = xmin; x < xmax; x++) {
-        set_alpha(x, y, copy.get_alpha(x - xmin + xfrom, y - ymin + yfrom));
+        set_xel(x, y, copy.get_xel(x - xmin + xfrom, y - ymin + yfrom));
+      }
+    }
+    
+    if (has_alpha() && copy.has_alpha()) {
+      for (y = ymin; y < ymax; y++) {
+        for (x = xmin; x < xmax; x++) {
+          set_alpha(x, y, copy.get_alpha(x - xmin + xfrom, y - ymin + yfrom));
+        }
       }
     }
   }
@@ -627,44 +651,182 @@ copy_sub_image(const PNMImage &copy, int xto, int yto,
 //               the destination image, instead of overwriting pixels
 //               unconditionally.
 //
-//               If the copy has no alpha channel, this degenerates
-//               into copy_sub_image().
+//               If pixel_scale is not 1.0, it specifies an amount to
+//               scale each *alpha* value of the source image before
+//               applying it to the target image.
+//
+//               If pixel_scale is 1.0 and the copy has no alpha
+//               channel, this degenerates into copy_sub_image().
 ////////////////////////////////////////////////////////////////////
 void PNMImage::
 blend_sub_image(const PNMImage &copy, int xto, int yto,
-                int xfrom, int yfrom, int x_size, int y_size) {
-  if (!copy.has_alpha()) {
+                int xfrom, int yfrom, int x_size, int y_size,
+                double pixel_scale) {
+  if (!copy.has_alpha() && pixel_scale == 1.0) {
     copy_sub_image(copy, xto, yto, xfrom, yfrom, x_size, y_size);
     return;
   }
 
-  if (xfrom < 0) {
-    xto += -xfrom;
-    xfrom = 0;
-  }
-  if (yfrom < 0) {
-    yto += -yfrom;
-    yfrom = 0;
+  int xmin, ymin, xmax, ymax;
+  setup_sub_image(copy, xto, yto, xfrom, yfrom, x_size, y_size,
+                  xmin, ymin, xmax, ymax);
+
+  int x, y;
+  if (copy.has_alpha()) {
+    for (y = ymin; y < ymax; y++) {
+      for (x = xmin; x < xmax; x++) {
+        blend(x, y, copy.get_xel(x - xmin + xfrom, y - ymin + yfrom),
+              copy.get_alpha(x - xmin + xfrom, y - ymin + yfrom) * pixel_scale);
+      }
+    }
+  } else {
+    for (y = ymin; y < ymax; y++) {
+      for (x = xmin; x < xmax; x++) {
+        blend(x, y, copy.get_xel(x - xmin + xfrom, y - ymin + yfrom),
+              pixel_scale);
+      }
+    }
   }
+}
 
-  x_size = (x_size < 0) ?
-    copy.get_x_size() :
-    min(x_size, copy.get_x_size() - xfrom);
-  y_size = (y_size < 0) ?
-    copy.get_y_size() :
-    min(y_size, copy.get_y_size() - yfrom);
+////////////////////////////////////////////////////////////////////
+//     Function: PNMImage::darken_sub_image
+//       Access: Published
+//  Description: Behaves like copy_sub_image(), but the resulting
+//               color will be the darker of the source and
+//               destination colors at each pixel (and at each R, G,
+//               B, A component value).
+//
+//               If pixel_scale is not 1.0, it specifies an amount to
+//               scale each pixel value of the source image before
+//               applying it to the target image.  The scale is
+//               applied with the center at 1.0: scaling the pixel
+//               value smaller brings it closer to 1.0.
+////////////////////////////////////////////////////////////////////
+void PNMImage::
+darken_sub_image(const PNMImage &copy, int xto, int yto,
+                 int xfrom, int yfrom, int x_size, int y_size,
+                 double pixel_scale) {
+  int xmin, ymin, xmax, ymax;
+  setup_sub_image(copy, xto, yto, xfrom, yfrom, x_size, y_size,
+                  xmin, ymin, xmax, ymax);
+
+  if (get_maxval() == copy.get_maxval() && pixel_scale == 1.0) {
+    // The simple case: no pixel value rescaling is required.
+    int x, y;
+    for (y = ymin; y < ymax; y++) {
+      for (x = xmin; x < xmax; x++) {
+        xel c = copy.get_xel_val(x - xmin + xfrom, y - ymin + yfrom);
+        xel o = get_xel_val(x, y);
+        xel p;
+        PPM_ASSIGN(p, min(c.r, o.r), min(c.g, o.g), min(c.b, o.b));
+        set_xel_val(x, y, p);
+      }
+    }
+    
+    if (has_alpha() && copy.has_alpha()) {
+      for (y = ymin; y < ymax; y++) {
+        for (x = xmin; x < xmax; x++) {
+          xelval c = copy.get_alpha_val(x - xmin + xfrom, y - ymin + yfrom);
+          xelval o = get_alpha_val(x, y);
+          set_alpha_val(x, y, min(c, o));
+        }
+      }
+    }
 
-  int xmin = max(0, xto);
-  int ymin = max(0, yto);
+  } else {
+    // The harder case: rescale pixel values according to maxval.
+    int x, y;
+    for (y = ymin; y < ymax; y++) {
+      for (x = xmin; x < xmax; x++) {
+        RGBColord c = copy.get_xel(x - xmin + xfrom, y - ymin + yfrom);
+        RGBColord o = get_xel(x, y);
+        RGBColord p;
+        p.set(min(1.0 - ((1.0 - c[0]) * pixel_scale), o[0]), 
+              min(1.0 - ((1.0 - c[1]) * pixel_scale), o[1]), 
+              min(1.0 - ((1.0 - c[2]) * pixel_scale), o[2]));
+        set_xel(x, y, p);
+      }
+    }
+    
+    if (has_alpha() && copy.has_alpha()) {
+      for (y = ymin; y < ymax; y++) {
+        for (x = xmin; x < xmax; x++) {
+          double c = copy.get_alpha(x - xmin + xfrom, y - ymin + yfrom);
+          double o = get_alpha(x, y);
+          set_alpha(x, y, min(1.0 - ((1.0 - c) * pixel_scale), o));
+        }
+      }
+    }
+  }
+}
 
-  int xmax = min(xmin + x_size, get_x_size());
-  int ymax = min(ymin + y_size, get_y_size());
+////////////////////////////////////////////////////////////////////
+//     Function: PNMImage::lighten_sub_image
+//       Access: Published
+//  Description: Behaves like copy_sub_image(), but the resulting
+//               color will be the lighter of the source and
+//               destination colors at each pixel (and at each R, G,
+//               B, A component value).
+//
+//               If pixel_scale is not 1.0, it specifies an amount to
+//               scale each pixel value of the source image before
+//               applying it to the target image.
+////////////////////////////////////////////////////////////////////
+void PNMImage::
+lighten_sub_image(const PNMImage &copy, int xto, int yto,
+                  int xfrom, int yfrom, int x_size, int y_size,
+                  double pixel_scale) {
+  int xmin, ymin, xmax, ymax;
+  setup_sub_image(copy, xto, yto, xfrom, yfrom, x_size, y_size,
+                  xmin, ymin, xmax, ymax);
+
+  if (get_maxval() == copy.get_maxval() && pixel_scale == 1.0) {
+    // The simple case: no pixel value rescaling is required.
+    int x, y;
+    for (y = ymin; y < ymax; y++) {
+      for (x = xmin; x < xmax; x++) {
+        xel c = copy.get_xel_val(x - xmin + xfrom, y - ymin + yfrom);
+        xel o = get_xel_val(x, y);
+        xel p;
+        PPM_ASSIGN(p, max(c.r, o.r), max(c.g, o.g), max(c.b, o.b));
+        set_xel_val(x, y, p);
+      }
+    }
+    
+    if (has_alpha() && copy.has_alpha()) {
+      for (y = ymin; y < ymax; y++) {
+        for (x = xmin; x < xmax; x++) {
+          xelval c = copy.get_alpha_val(x - xmin + xfrom, y - ymin + yfrom);
+          xelval o = get_alpha_val(x, y);
+          set_alpha_val(x, y, max(c, o));
+        }
+      }
+    }
 
-  int x, y;
-  for (y = ymin; y < ymax; y++) {
-    for (x = xmin; x < xmax; x++) {
-      blend(x, y, copy.get_xel(x - xmin + xfrom, y - ymin + yfrom),
-            copy.get_alpha(x - xmin + xfrom, y - ymin + yfrom));
+  } else {
+    // The harder case: rescale pixel values according to maxval.
+    int x, y;
+    for (y = ymin; y < ymax; y++) {
+      for (x = xmin; x < xmax; x++) {
+        RGBColord c = copy.get_xel(x - xmin + xfrom, y - ymin + yfrom);
+        RGBColord o = get_xel(x, y);
+        RGBColord p;
+        p.set(max(c[0] * pixel_scale, o[0]), 
+              max(c[1] * pixel_scale, o[1]), 
+              max(c[2] * pixel_scale, o[2]));
+        set_xel(x, y, p);
+      }
+    }
+    
+    if (has_alpha() && copy.has_alpha()) {
+      for (y = ymin; y < ymax; y++) {
+        for (x = xmin; x < xmax; x++) {
+          double c = copy.get_alpha(x - xmin + xfrom, y - ymin + yfrom);
+          double o = get_alpha(x, y);
+          set_alpha(x, y, max(c * pixel_scale, o));
+        }
+      }
     }
   }
 }
@@ -678,14 +840,18 @@ blend_sub_image(const PNMImage &copy, int xto, int yto,
 //
 //               The min_radius and max_radius are in the scale 0..1,
 //               where 1.0 means the full width of the image.  If
-//               min_radius == max_radius, there is no fuzzy edge;
-//               otherwise, the pixels between min_radius and
-//               max_radius are smoothly blended between fg and bg
-//               colors.
+//               min_radius == max_radius, the edge is sharp (but
+//               still antialiased); otherwise, the pixels between
+//               min_radius and max_radius are smoothly blended
+//               between fg and bg colors.
 ////////////////////////////////////////////////////////////////////
 void PNMImage::
 render_spot(const Colord &fg, const Colord &bg,
             double min_radius, double max_radius) {
+  if (_x_size == 0 || _y_size == 0) {
+    return;
+  }
+
   double x_scale = 2.0 / _x_size;
   double y_scale = 2.0 / _y_size;
 
@@ -701,27 +867,46 @@ render_spot(const Colord &fg, const Colord &bg,
 
   for (int yi = 0; yi < y_center1; ++yi) {
     double y = yi * y_scale;
+    double y2_inner = y * y;
+    double y2_outer = (y + y_scale) * (y + y_scale);
     for (int xi = 0; xi < x_center1; ++xi) {
       double x = xi * x_scale;
-      double d2 = (x * x + y * y);
-      if (d2 <= min_r2) {
+      double d2_inner = (x * x + y2_inner);
+      double d2_outer = ((x + x_scale) * (x + x_scale) + y2_outer);
+      double d2_a = ((x + x_scale) * (x + x_scale) + y2_inner);
+      double d2_b = (x * x + y2_outer);
+
+      if ((d2_inner <= min_r2) &&
+          (d2_outer <= min_r2) &&
+          (d2_a <= min_r2) &&
+          (d2_b <= min_r2)) {
+        // This pixel is solidly in the center of the spot.
         set_xel_a(x_center1 - 1 - xi, y_center1 - 1 - yi, fg);
         set_xel_a(x_center0 + xi, y_center1 - 1 - yi, fg);
         set_xel_a(x_center1 - 1 - xi, y_center0 + yi, fg);
         set_xel_a(x_center0 + xi, y_center0 + yi, fg);
 
-      } else if (d2 >= max_r2) {
+      } else if ((d2_inner > max_r2) &&
+                 (d2_outer > max_r2) &&
+                 (d2_a > max_r2) &&
+                 (d2_b > max_r2)) {
+        // This pixel is solidly outside the spot.
         set_xel_a(x_center1 - 1 - xi, y_center1 - 1 - yi, bg);
         set_xel_a(x_center0 + xi, y_center1 - 1 - yi, bg);
         set_xel_a(x_center1 - 1 - xi, y_center0 + yi, bg);
         set_xel_a(x_center0 + xi, y_center0 + yi, bg);
 
       } else {
-        double d = sqrt(d2);
-        d = (d - min_radius) / (max_radius - min_radius);
-        d2 = d * d;
-        double t = (3.0 * d2) - (2.0 * d * d2);
-        Colord c = fg + t * (bg - fg);
+        // This pixel is in a feathered area or along the antialiased edge.
+        Colord c_outer, c_inner, c_a, c_b;
+        compute_spot_pixel(c_outer, d2_outer, min_radius, max_radius, fg, bg);
+        compute_spot_pixel(c_inner, d2_inner, min_radius, max_radius, fg, bg);
+        compute_spot_pixel(c_a, d2_a, min_radius, max_radius, fg, bg);
+        compute_spot_pixel(c_b, d2_b, min_radius, max_radius, fg, bg);
+
+        // Now average all four pixels for the antialiased result.
+        Colord c;
+        c = (c_outer + c_inner + c_a + c_b) * 0.25;
 
         set_xel_a(x_center1 - 1 - xi, y_center1 - 1 - yi, c);
         set_xel_a(x_center0 + xi, y_center1 - 1 - yi, c);
@@ -732,10 +917,34 @@ render_spot(const Colord &fg, const Colord &bg,
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PNMImage::expand_border
+//       Access: Published
+//  Description: Expands the image by the indicated number of pixels
+//               on each edge.  The new pixels are set to the
+//               indicated color.
+//
+//               If any of the values is negative, this actually crops
+//               the image.
+////////////////////////////////////////////////////////////////////
+void PNMImage::
+expand_border(int left, int right, int bottom, int top,
+              const Colord &color) {
+  PNMImage new_image(get_x_size() + left + right,
+                     get_y_size() + bottom + top,
+                     get_num_channels(), get_maxval(), get_type());
+  new_image.fill(color[0], color[1], color[2]);
+  if (has_alpha()) {
+    new_image.alpha_fill(color[3]);
+  }
+  new_image.copy_sub_image(*this, left, top);
+
+  take_from(new_image);
+}
 
 ////////////////////////////////////////////////////////////////////
 //     Function: PNMImage::setup_rc
-//       Access: Published
+//       Access: Private
 //  Description: Sets the _default_rc,bc,gc values appropriately
 //               according to the color type of the image, so that
 //               get_bright() will return a meaningful value for both

+ 23 - 2
panda/src/pnmimage/pnmImage.h

@@ -22,6 +22,7 @@
 #include "pandabase.h"
 
 #include "pnmImageHeader.h"
+#include "pnmBrush.h"
 
 #include "luse.h"
 
@@ -77,6 +78,7 @@ PUBLISHED:
 
   void copy_from(const PNMImage &copy);
   void copy_header_from(const PNMImageHeader &header);
+  void take_from(PNMImage &orig);
 
   INLINE void fill(double red, double green, double blue);
   INLINE void fill(double gray = 0.0);
@@ -181,17 +183,28 @@ PUBLISHED:
   INLINE xel *operator [] (int y);
   INLINE const xel *operator [] (int y) const;
 
-
   void copy_sub_image(const PNMImage &copy, int xto, int yto,
                       int xfrom = 0, int yfrom = 0,
                       int x_size = -1, int y_size = -1);
   void blend_sub_image(const PNMImage &copy, int xto, int yto,
                        int xfrom = 0, int yfrom = 0,
-                       int x_size = -1, int y_size = -1);
+                       int x_size = -1, int y_size = -1,
+                       double pixel_scale = 1.0);
+  void darken_sub_image(const PNMImage &copy, int xto, int yto,
+                        int xfrom = 0, int yfrom = 0,
+                        int x_size = -1, int y_size = -1,
+                        double pixel_scale = 1.0);
+  void lighten_sub_image(const PNMImage &copy, int xto, int yto,
+                         int xfrom = 0, int yfrom = 0,
+                         int x_size = -1, int y_size = -1,
+                         double pixel_scale = 1.0);
 
   void render_spot(const Colord &fg, const Colord &bg,
                    double min_radius, double max_radius);
 
+  void expand_border(int left, int right, int bottom, int top,
+                     const Colord &color);
+
   // The bodies for the non-inline *_filter() functions can be found
   // in the file pnm-image-filter.cxx.
 
@@ -210,6 +223,14 @@ private:
   INLINE xel *row(int row) const;
   INLINE xelval *alpha_row(int row) const;
 
+  INLINE void setup_sub_image(const PNMImage &copy, int &xto, int &yto,
+                              int &xfrom, int &yfrom, int &x_size, int &y_size,
+                              int &xmin, int &ymin, int &xmax, int &ymax);
+
+  INLINE static void compute_spot_pixel(Colord &c, double d2, 
+                                        double min_radius, double max_radius,
+                                        const Colord &fg, const Colord &bg);
+
   void setup_rc();
 
 private:

+ 131 - 0
panda/src/pnmimage/pnmPainter.I

@@ -0,0 +1,131 @@
+// Filename: pnmPainter.I
+// Created by:  drose (02Feb07)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+ 
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMPainter::Destructor
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE PNMPainter::
+~PNMPainter() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMPainter::set_pen
+//       Access: Published
+//  Description: Specifies a PNMBrush that will be used for drawing
+//               lines and edges.  If the brush is a bitmap brush, its
+//               image will be smeared pixelwise along the line.
+//
+//               Unlike the PNMImage passed to the constructor, the
+//               PNMPainter will take ownership of the pen.  It is not
+//               necessary to keep a separate pointer to it.
+////////////////////////////////////////////////////////////////////
+INLINE void PNMPainter::
+set_pen(PNMBrush *pen) {
+  _pen = pen;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMPainter::get_pen
+//       Access: Published
+//  Description: Returns the current pen.  See set_pen().
+////////////////////////////////////////////////////////////////////
+INLINE PNMBrush *PNMPainter::
+get_pen() const {
+  return _pen;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMPainter::set_fill
+//       Access: Published
+//  Description: Specifies a PNMBrush that will be used for filling
+//               in the interiors of objects.  If the brush is a
+//               bitmap brush, its image will be tiled throughout the
+//               space.
+//
+//               Unlike the PNMImage passed to the constructor, the
+//               PNMPainter will take ownership of the fill brush.  It
+//               is not necessary to keep a separate pointer to it.
+////////////////////////////////////////////////////////////////////
+INLINE void PNMPainter::
+set_fill(PNMBrush *fill) {
+  _fill = fill;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMPainter::get_fill
+//       Access: Published
+//  Description: Returns the current fill brush.  See set_fill().
+////////////////////////////////////////////////////////////////////
+INLINE PNMBrush *PNMPainter::
+get_fill() const {
+  return _fill;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMPainter::draw_point
+//       Access: Published
+//  Description: Draws an antialiased point on the PNMImage, using the
+//               current pen.
+////////////////////////////////////////////////////////////////////
+INLINE void PNMPainter::
+draw_point(double x, double y) {
+  draw_line(x, y, x, y);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMPainter::draw_hline_point
+//       Access: Private
+//  Description: Called within draw_line() to draw a single point of a
+//               mostly-horizontal line.
+////////////////////////////////////////////////////////////////////
+INLINE void PNMPainter::
+draw_hline_point(int x, double xa, double ya, double xd, double yd,
+                 double pixel_scale) {
+  double y = (yd * (x - xa) / xd) + ya;
+  int ymax = (int)cceil(y);
+  int ymin = (int)cfloor(y);
+  if (ymax == ymin) {
+    _pen->draw(_image, x, ymin, pixel_scale);
+  } else {
+    _pen->draw(_image, x, ymax, (y - ymin) * pixel_scale);
+    _pen->draw(_image, x, ymin, (ymax - y) * pixel_scale);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMPainter::draw_vline_point
+//       Access: Private
+//  Description: Called within draw_line() to draw a single point of a
+//               mostly-vertical line.
+////////////////////////////////////////////////////////////////////
+INLINE void PNMPainter::
+draw_vline_point(int y, double xa, double ya, double xd, double yd,
+                 double pixel_scale) {
+  double x = (xd * (y - ya) / yd) + xa;
+  int xmax = (int)cceil(x);
+  int xmin = (int)cfloor(x);
+  if (xmax == xmin) {
+    _pen->draw(_image, xmin, y, pixel_scale);
+  } else {
+    _pen->draw(_image, xmax, y, (x - xmin) * pixel_scale);
+    _pen->draw(_image, xmin, y, (xmax - x) * pixel_scale);
+  }
+}

+ 190 - 0
panda/src/pnmimage/pnmPainter.cxx

@@ -0,0 +1,190 @@
+// Filename: pnmPainter.cxx
+// Created by:  drose (02Feb07)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "pnmPainter.h"
+#include "pnmBrush.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMPainter::Constructor
+//       Access: Published
+//  Description: The constructor stores a pointer to the PNMImage you
+//               pass it, but it does not take ownership of the
+//               object; you are responsible for ensuring that the
+//               PNMImage does not destruct during the lifetime of the
+//               PNMPainter object.
+//
+//               The xo, yo coordinates specify an optional offset for
+//               fill coordinates.  If you are painting with a pattern
+//               fill, these specify the virtual coordinates of the
+//               upper-left corner of the image, which can allow you
+//               to adjust the pattern to line up with nested images,
+//               if necessary.
+////////////////////////////////////////////////////////////////////
+PNMPainter::
+PNMPainter(PNMImage &image, int xo, int yo) :
+  _image(image),
+  _xo(xo), _yo(yo)
+{
+  _pen = PNMBrush::make_pixel(Colord(0, 0, 0, 1));
+  _fill = PNMBrush::make_pixel(Colord(1, 1, 1, 1));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMPainter::draw_line
+//       Access: Published
+//  Description: Draws an antialiased line on the PNMImage, using the
+//               current pen.
+////////////////////////////////////////////////////////////////////
+void PNMPainter::
+draw_line(double xa, double ya, double xb, double yb) {
+  // Shift the line coordinates to position the center of the pen on
+  // the line.
+  xa -= (_pen->get_xc() - 0.5);
+  xb -= (_pen->get_xc() - 0.5);
+  ya -= (_pen->get_yc() - 0.5);
+  yb -= (_pen->get_yc() - 0.5);
+
+  // Compute the line delta.
+  double xd = xb - xa;
+  double yd = yb - ya;
+
+  if (xa == xb && ya == yb) {
+    // Just a single point.  Treat it as a very short horizontal line.
+    xd = 1.0;
+  }
+
+  if (cabs(xd) > cabs(yd)) {
+    // This line is more horizontal than vertical.
+    if (xa < xb) {
+      // Draw the line from left to right.
+      int x_min = (int)cfloor(xa);
+      int x_max = (int)cceil(xb);
+
+      // The first point.
+      draw_hline_point(x_min, xa, ya, xd, yd, 1.0 - (xa - x_min));
+
+      // The middle points.
+      for (int x = x_min + 1; x < x_max; ++x) {
+        draw_hline_point(x, xa, ya, xd, yd, 1.0);
+      }
+
+      if (x_max != x_min) {
+        // The last point.
+        draw_hline_point(x_max, xa, ya, xd, yd, 1.0 - (x_max - xb));
+      }
+
+    } else {
+      // Draw the line from right to left.
+      int x_min = (int)cfloor(xb);
+      int x_max = (int)cceil(xa);
+
+      // The first point.
+      draw_hline_point(x_max, xa, ya, xd, yd, 1.0 - (x_max - xa));
+
+      // The middle points.
+      for (int x = x_max - 1; x > x_min; --x) {
+        draw_hline_point(x, xa, ya, xd, yd, 1.0);
+      }
+
+      if (x_max != x_min) {
+        // The last point.
+        draw_hline_point(x_min, xa, ya, xd, yd, 1.0 - (xb - x_min));
+      }
+    }
+
+  } else {
+    // This line is more vertical than horizontal.
+    if (ya < yb) {
+      // Draw the line from top to bottom.
+      int y_min = (int)cfloor(ya);
+      int y_max = (int)cceil(yb);
+
+      // The first point.
+      draw_vline_point(y_min, xa, ya, xd, yd, 1.0 - (ya - y_min));
+
+      // The middle points.
+      for (int y = y_min + 1; y < y_max; ++y) {
+        draw_vline_point(y, xa, ya, xd, yd, 1.0);
+      }
+
+      if (y_max != y_min) {
+        // The last point.
+        draw_vline_point(y_max, xa, ya, xd, yd, 1.0 - (y_max - yb));
+      }
+
+    } else {
+      // Draw the line from bottom to top.
+      int y_min = (int)cfloor(yb);
+      int y_max = (int)cceil(ya);
+
+      // The first point.
+      draw_vline_point(y_max, xa, ya, xd, yd, 1.0 - (y_max - ya));
+
+      // The middle points.
+      for (int y = y_max - 1; y > y_min; --y) {
+        draw_vline_point(y, xa, ya, xd, yd, 1.0);
+      }
+
+      if (y_max != y_min) {
+        // The last point.
+        draw_vline_point(y_min, xa, ya, xd, yd, 1.0 - (yb - y_min));
+      }
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMPainter::draw_rectangle
+//       Access: Published
+//  Description: Draws a filled rectangule on the PNMImage, using the
+//               current pen for the outline, and the current fill
+//               brush for the interior.
+//
+//               The two coordinates specify any two diagonally
+//               opposite corners.
+////////////////////////////////////////////////////////////////////
+void PNMPainter::
+draw_rectangle(double xa, double ya, double xb, double yb) {
+  // Make (xa, ya) be the upper-left corner, and (xb, yb) the
+  // lower-right.
+  if (xa > xb) {
+    double t = xa;
+    xa = xb;
+    xb = t;
+  }
+  if (ya > yb) {
+    double t = ya;
+    ya = yb;
+    yb = t;
+  }
+
+  // First, fill the interior.
+  int x_min = (int)cceil(xa);
+  int x_max = (int)cfloor(xb);
+  int y_min = (int)cceil(ya);
+  int y_max = (int)cfloor(yb);
+  for (int y = y_min; y <= y_max; ++y) {
+    _fill->fill(_image, x_min, x_max, y, _xo, _yo);
+  }
+
+  // Then, draw the outline.
+  draw_line(xa, ya, xa, yb);
+  draw_line(xa, yb, xb, yb);
+  draw_line(xb, yb, xb, ya);
+  draw_line(xb, ya, xa, ya);
+}

+ 69 - 0
panda/src/pnmimage/pnmPainter.h

@@ -0,0 +1,69 @@
+// Filename: pnmPainter.h
+// Created by:  drose (02Feb07)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef PNMPAINTER_H
+#define PNMPAINTER_H
+
+#include "pandabase.h"
+
+class PNMImage;
+
+////////////////////////////////////////////////////////////////////
+//       Class : PNMPainter
+// Description : This class provides a number of convenient methods
+//               for painting drawings directly into a PNMImage.
+//
+//               It stores a pointer to the PNMImage you pass it, but
+//               it does not take ownership of the object; you are
+//               responsible for ensuring that the PNMImage does not
+//               destruct during the lifetime of the PNMPainter
+//               object.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA PNMPainter {
+PUBLISHED:
+  PNMPainter(PNMImage &image, int xo = 0, int yo = 0);
+  INLINE ~PNMPainter();
+
+  INLINE void set_pen(PNMBrush *pen);
+  INLINE PNMBrush *get_pen() const;
+  INLINE void set_fill(PNMBrush *fill);
+  INLINE PNMBrush *get_fill() const;
+
+  INLINE void draw_point(double x, double y);
+  void draw_line(double xa, double ya, double xb, double yb);
+  void draw_rectangle(double xa, double ya, double xb, double yb);
+
+private:
+  INLINE void draw_hline_point(int x, double xa, double ya, 
+                               double xd, double yd,
+                               double pixel_scale);
+  INLINE void draw_vline_point(int y, double xa, double ya, 
+                               double xd, double yd,
+                               double pixel_scale);
+
+private:
+  PNMImage &_image;
+  int _xo, _yo;
+
+  PT(PNMBrush) _pen;
+  PT(PNMBrush) _fill;
+};
+
+#include "pnmPainter.I"
+
+#endif

+ 1 - 0
panda/src/pnmimage/pnmimage_composite1.cxx

@@ -1,5 +1,6 @@
 #include "config_pnmimage.cxx"
 #include "pnm-image-filter.cxx"
 #include "pnmbitio.cxx"
+#include "pnmBrush.cxx"
 #include "pnmFileType.cxx"
 

+ 1 - 0
panda/src/pnmimage/pnmimage_composite2.cxx

@@ -1,5 +1,6 @@
 #include "pnmImage.cxx"
 #include "pnmImageHeader.cxx"
+#include "pnmPainter.cxx"
 #include "pnmReader.cxx"
 #include "pnmWriter.cxx"
 #include "pnmFileTypeRegistry.cxx"