diff --git a/panda/src/pnmimage/Sources.pp b/panda/src/pnmimage/Sources.pp index 3980e96b5e..8e152c7402 100644 --- a/panda/src/pnmimage/Sources.pp +++ b/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 diff --git a/panda/src/pnmimage/pnmBrush.I b/panda/src/pnmimage/pnmBrush.I new file mode 100644 index 0000000000..2a2229fdce --- /dev/null +++ b/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 +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// 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; +} diff --git a/panda/src/pnmimage/pnmBrush.cxx b/panda/src/pnmimage/pnmBrush.cxx new file mode 100644 index 0000000000..edd12fc5e1 --- /dev/null +++ b/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 +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + +#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); +} diff --git a/panda/src/pnmimage/pnmBrush.h b/panda/src/pnmimage/pnmBrush.h new file mode 100644 index 0000000000..542a5f83bb --- /dev/null +++ b/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 +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + +#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 diff --git a/panda/src/pnmimage/pnmImage.I b/panda/src/pnmimage/pnmImage.I index bc7e210843..3c287944ab 100644 --- a/panda/src/pnmimage/pnmImage.I +++ b/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 ©, 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; + } +} diff --git a/panda/src/pnmimage/pnmImage.cxx b/panda/src/pnmimage/pnmImage.cxx index 176346d924..b1313a2ce4 100644 --- a/panda/src/pnmimage/pnmImage.cxx +++ b/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 ©) { 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 ©, 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; - } + int xmin, ymin, xmax, ymax; + setup_sub_image(copy, xto, yto, xfrom, yfrom, x_size, y_size, + xmin, ymin, xmax, ymax); - 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 = 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 (has_alpha() && copy.has_alpha()) { + 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_alpha(x, y, copy.get_alpha(x - xmin + xfrom, y - ymin + yfrom)); + 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)); + } + } + } + + } 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_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 ©, 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 ©, 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; - } - - 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 = 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 xmin, ymin, xmax, ymax; + setup_sub_image(copy, xto, yto, xfrom, yfrom, x_size, y_size, + xmin, ymin, xmax, ymax); 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)); + 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); + } + } + } +} + +//////////////////////////////////////////////////////////////////// +// 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 ©, 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)); + } + } + } + + } 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)); + } + } + } + } +} + +//////////////////////////////////////////////////////////////////// +// 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 ©, 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)); + } + } + } + + } 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 ©, 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 diff --git a/panda/src/pnmimage/pnmImage.h b/panda/src/pnmimage/pnmImage.h index 707412df3a..30f500af2e 100644 --- a/panda/src/pnmimage/pnmImage.h +++ b/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 ©); 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 ©, int xto, int yto, int xfrom = 0, int yfrom = 0, int x_size = -1, int y_size = -1); void blend_sub_image(const PNMImage ©, 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 ©, 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 ©, 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 ©, 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: diff --git a/panda/src/pnmimage/pnmPainter.I b/panda/src/pnmimage/pnmPainter.I new file mode 100644 index 0000000000..7ad74ea7d7 --- /dev/null +++ b/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 +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// 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); + } +} diff --git a/panda/src/pnmimage/pnmPainter.cxx b/panda/src/pnmimage/pnmPainter.cxx new file mode 100644 index 0000000000..142c1c2e04 --- /dev/null +++ b/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 +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + +#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); +} diff --git a/panda/src/pnmimage/pnmPainter.h b/panda/src/pnmimage/pnmPainter.h new file mode 100644 index 0000000000..185e288b0a --- /dev/null +++ b/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 +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + +#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 diff --git a/panda/src/pnmimage/pnmimage_composite1.cxx b/panda/src/pnmimage/pnmimage_composite1.cxx index 91512f8da9..0a2a6c01f1 100644 --- a/panda/src/pnmimage/pnmimage_composite1.cxx +++ b/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" diff --git a/panda/src/pnmimage/pnmimage_composite2.cxx b/panda/src/pnmimage/pnmimage_composite2.cxx index fde48347f7..a005eec42e 100644 --- a/panda/src/pnmimage/pnmimage_composite2.cxx +++ b/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"