From e0c6872cf40896c7be36b11dcc744620f10adf1d Mon Sep 17 00:00:00 2001 From: Norbert Preining Date: Mon, 2 Sep 2019 13:46:59 +0900 Subject: Initial commit --- dviware/dvi2bitmap/Bitmap.cc | 1305 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1305 insertions(+) create mode 100644 dviware/dvi2bitmap/Bitmap.cc (limited to 'dviware/dvi2bitmap/Bitmap.cc') diff --git a/dviware/dvi2bitmap/Bitmap.cc b/dviware/dvi2bitmap/Bitmap.cc new file mode 100644 index 0000000000..9230e4ae04 --- /dev/null +++ b/dviware/dvi2bitmap/Bitmap.cc @@ -0,0 +1,1305 @@ +/* This file is part of dvi2bitmap; see README for copyrights and licence */ + +// There are problems with the implementation here, of which the most +// pronounced is the way that cropping and scaling down are muddled +// together. Partly, this is because the implementations of these +// features are not cleanly separated. Better, I think, would be to +// have the crop() and scaleDown() methods simply request the +// corresponding features, leaving the actual transformations to be +// done immediately before write(), for example. +// +// Also attractive (and easier, after that) would be a way to have the +// crop margins specifiable in a fuller range of units. + +#include + +#include // debug code writes to cerr +#include + +#ifdef HAVE_CSTD_INCLUDE +#include +#include // g++ doesn't have +#include +#include +#include // for memcpy and friends +#else +#include +#include +#include +#include +#include +#endif + +using STD::cout; +using STD::cerr; +using STD::endl; + +#include "Bitmap.h" +#include "BitmapImage.h" +#include "DviFilePosition.h" + +// Declare static variables +verbosities Bitmap::verbosity_ = normal; +int Bitmap::cropMarginDefault[4] = { 0, 0, 0, 0 }; +bool Bitmap::cropMarginAbsDefault[4] = {false, false, false, false }; +Bitmap::BitmapColour Bitmap::def_fg_ = { 0, 0, 0}; +Bitmap::BitmapColour Bitmap::def_bg_ = {255, 255, 255}; +bool Bitmap::def_customRGB_ = false; +const char* Bitmap::logBitmapPrefix_ = 0; + +Bitmap::const_iterator Bitmap::endIterator_; + +// Indecision: Within scaleDown, it seems sensible to average the +// pixel values over the complete factor*factor square, even when +// we've strayed out of the bounding box. However, this sometimes makes +// the edges of images look too faint. If SCALEDOWN_COMPLETE_AVERAGE +// is 1, then average over the whole square; if it's 0, then average +// only over those pixels which are actually on the bitmap. +#define SCALEDOWN_COMPLETE_AVERAGE 0 + +/** + * Create a new bitmap with the given parameters. + * + *

Coordinates on the bitmap run from 0 to W-1, and 0 to H-1, + * with point (0,0) in the top-left corner, the x-axis + * increasing to the right, and the y-axis increasing downwards. + * + * @param w the width of the bitmap, in pixels + * + * @param h the height of the bitmap, in pixels + * + * @param bpp the number of bits-per-pixel (default is 1) + * + * @param expandable if true (the default), the bitmap is expandable; + * if false, the bitmap is fixed at the specified size + * + * @param maxwidth if expandable is true, and + * maxwidth is greater than or equal to w, + * this is the maximum horizontal size the bitmap will expand to; if + * it is less than w (which includes negative, the + * default), the maximum width is set to a default multiplier of the width + * w + * + * @param maxheight if expandable is true, and + * maxheight is greater than or equal to h, + * this is the maximum vertical size the bitmap will expand to; if + * maxheight is less than h (which includes + * negative, the default), the maximum vertical size will be such that + * maxheight/h==maxwidth/w + * + * @throws BitmapError if the arguments are inconsistent + */ +Bitmap::Bitmap (const int w, const int h, const int bpp, + bool expandable, + const int maxwidth, const int maxheight) + throw (BitmapError) + : W(w), H(h), isExpandable_(expandable), + frozen_(false), mark_(0), transparent_(false), + customRGB_(false), bpp_(bpp) +{ + if (W <= 0 || H <= 0) + throw BitmapError("Bitmap constructor called with negative size!"); + + B = new Byte[W*H]; + + clear(); + + if (isExpandable_) { + if (maxwidth >= w) + maxW_ = maxwidth; + else + maxW_ = 10*W; // default upper limit on expansion + if (maxheight >= h) + maxH_ = maxheight; + else + maxH_ = h*maxW_/w + 1; // round up + } + + if (def_customRGB_) + { + fg_.red = def_fg_.red; + fg_.green = def_fg_.green; + fg_.blue = def_fg_.blue; + bg_.red = def_bg_.red; + bg_.green = def_bg_.green; + bg_.blue = def_bg_.blue; + if (verbosity_ > normal) + cerr << "Bitmap::Bitmap: Custom RGB:" + << static_cast(fg_.red) << ',' + << static_cast(fg_.green) << ',' + << static_cast(fg_.blue) << '/' + << static_cast(bg_.red) << ',' + << static_cast(bg_.green) << ',' + << static_cast(bg_.blue) << endl; + customRGB_ = true; + } + + if (bpp_ > 8) + // too big for a Byte... + bpp_ = 8; + max_colour_ = static_cast((1< normal) + cerr << "Bitmap::new Bitmap(W=" + << W << ", H=" << H << ", bpp=" << bpp_ << ")" << endl; +} + +Bitmap::~Bitmap() +{ + delete[] B; + if (mark_ != 0) + delete mark_; +} + +/** + * Resets the bitmap to its initial state. This clears the bitmap by + * setting all the pixels to white, unfreezing it, and resetting the + * bounding box and crops to their initial states. It does not + * deallocate any memory, however, so if the bitmap has expanded in + * the past, the reset bitmap is the same size. + * + *

It does not reset the transparency flag or adjust the colour + * setting, or reset the pixel depth. This latter behaviour + * may change in future. + */ +void Bitmap::clear() +{ + STD::memset ((void*)B, 0, W*H); + + bbL = bbT = INT_MAX; // numeric_limits::max(); + bbR = bbB = INT_MIN; // numeric_limits::min(); + + cropL = 0; + cropR = W; + cropT = 0; + cropB = H; + + cropMargin[Left] = cropMarginDefault[Left]; + cropMargin[Right] = cropMarginDefault[Right]; + cropMargin[Top] = cropMarginDefault[Top]; + cropMargin[Bottom] = cropMarginDefault[Bottom]; + cropMarginAbs[Left] = cropMarginAbsDefault[Left]; + cropMarginAbs[Right] = cropMarginAbsDefault[Right]; + cropMarginAbs[Top] = cropMarginAbsDefault[Top]; + cropMarginAbs[Bottom] = cropMarginAbsDefault[Bottom]; + + cropped_ = false; + scaled_ = false; + frozen_ = false; + // but don't reset transparent_ or customRGB_ + + if (mark_ != 0) { + delete mark_; + mark_ = 0; + } + + if (verbosity_ > normal) + cerr << "Bitmap::clear" << endl; +} + +/** + * Declares that a routine is about to draw in the rectangle with + * corners (ulx, uly) to (lrx, lry) + * (inclusive). If the bitmap is expandable, this should do + * any reallocations which are necessary or possible, and adjust W and + * H accordingly. + * + *

This does not (currently) allow any expansion towards negative + * coordinates. + */ +void Bitmap::usesBitmapArea_(const int ulx, const int uly, + const int lrx, const int lry) +{ + if (!isExpandable_) + return; // nothing to do + + const float magfactor = 1.5; + + int tW = W; + if (lrx > W) { + float tWf = tW; + assert (tWf > 0 && magfactor > 1); + while (tWf(STD::ceil(tWf)); + if (tW > maxW_) + tW = maxW_; + } + + int tH = H; + if (lry > H) { + float tHf = tH; + assert (tHf > 0 && magfactor > 1); + while (tHf(STD::ceil(tHf)); + if (tH > maxH_) + tH = maxH_; + } + + if (tW == maxW_ && tH == maxH_) { + // the bitmap can't be expanded any more after this + if (verbosity_ >= normal) + cerr << "Bitmap has reached maximum size, (" + << maxW_ << "," << maxH_ << "), no further expansion" << endl; + isExpandable_ = false; + } + + if (tW != W || tH != H) { + // We're expanding... + // There are a variety of ways to make this more efficient. + // But there's absolutely no need to bother with them yet. + Byte* oldB = B; + B = new Byte[tW*tH]; + STD::memset((void*)B, 0, tW*tH); + for (int row=0; row normal) { + cerr << "Bitmap:: expanded from (" << W << ',' << H << ") to (" + << tW << ',' << tH << "): max (" + << maxW_ << ',' << maxH_ << ")" << endl; + } + W = tW; + H = tH; + delete[] oldB; + } +} + + +/** + * Paint a bitmap onto the master bitmap. The bitmap to be added is + * given in a one-dimensional array b, which is + * w pixels wide and h high. Like the + * master bitmap, the x + * axis runs horizontally and the y axis vertically downwards. + * + *

The pixel at position (x,y) on the new bitmap is at position + * b[y*w+x] in the input bitmap array. This new bitmap is + * painted onto the master bitmap with its top left corner pixel + * (namely position (0,0)) occupying pixel (x,y) on + * the master bitmap, and pixel (a,b) occupying pixel + * (x+a,y+b) unless this would be off the master bitmap. + * + *

Any parts of the new bitmap falling outside the boundary of the + * master are cropped. + * + * @param x the pixel in the top-left corner of the new bitmap (coordinate + * (0,0)) is located at position (x,y) of the master + * bitmap + * @param y (see parameter x) + * @param w the width of the new bitmap, in pixels + * @param h the height of the new bitmap, in pixels + * @param b the new bitmap, as a one-dimensional array + * @throws BitmapError if this is called after method freeze() + */ +void Bitmap::paint(const int x, const int y, const int w, const int h, + const Byte *b) + throw (BitmapError) +{ + // At one time I added a note to the documentation of the 'x' + // parameter: 'XXX NO, should be the reference point!!!'. What + // does that mean -- I seem to have been rather emphatic about it, + // without much explaining it. + + // Set to max_colour_ any pixels in the master which are non-zero in the + // new bitmap, and crop any parts of the new bitmap + // falling outside the boundary of the master + // Update bb? as a side-effect. + if (frozen_) + throw BitmapError ("paint() called after freeze()"); + + usesBitmapArea_(x, y, x+w, y+h); + + // Paint [row1,row2-1] and [col1,col2-1] of the new bitmap into + // the master bitmap; + // if the new bitmap is entirely within the master, then + // row1=0, row2=H, col1=0, col2=W. The new bitmap is placed so + // that pixel (a,b) of the new bitmap + // is placed on pixel (x+a,y+b) of the master bitmap, unless this + // would place that pixel outside the boundary of the master. + // + // Note that this does the correct thing when x>W or y>H + // (ie, it makes col2<0, so loop is never started; same for row2). + int col1 = (x>=0 ? 0 : -x); + int col2 = (x+w<=W ? w : W-x); + int row1 = (y>=0 ? 0 : -y); + int row2 = (y+h<=H ? h : H-y); + + for (int row=row1; row bbR) bbR = x+w; + if (y < bbT) bbT = y; + if (y+h > bbB) bbB = y+h; + + if (verbosity_ > debug) + cerr << "Bitmap::paint @ (" << x << ',' << y << "): (0:" + << w << ",0:" << h << ") -> (" + << col1 << ':' << col2 << ',' << row1 << ':' << row2 + << "). BB now [" << bbL << ':' << bbR << "), [" + << bbT << ':' << bbB << ")" << endl; +} + + +/** + * Draws on the master bitmap a block (a `rule' in TeX terms) of + * height h and width w pixels. The bottom left corner of the rule + * occupies pixel (x,y) on the master bitmap. + * + * @param x the (pixel in the) bottom-left corner of the rule + * is located at position (x,y) of the master bitmap + * @param y (see parameter x) + * @param w the width of the new rule, in pixels + * @param h the height of the new rule, in pixels + * @throws BitmapError if this is called after method freeze() + */ +void Bitmap::rule(const int x, const int y, const int w, const int h) + throw (BitmapError) +{ + // Update bb? as a side-effect. + // OR the new pixels into place, and crop any parts of the new bitmap + // falling outside the boundary of the master + if (frozen_) + throw BitmapError ("rule() called after freeze()"); + + // OR everything in a block between [row1,row2-1] and + // [col1,col2-1], inclusive + int col1 = x; + int col2 = x+w; + int row1 = y+1-h; + int row2 = y+1; + + usesBitmapArea_(col1, row1, col2-1, row2-1); + + if (col1 < 0) col1 = 0; + if (col2 > W) col2 = W; + if (row1 < 0) row1 = 0; + if (row2 > H) row2 = H; + + + for (int row=row1; row bbR) bbR = col2; + if (row1 < bbT) bbT = row1; + if (row2 > bbB) bbB = row2; + + if (verbosity_ > normal) + cerr << "Bitmap::rule @ (" << x << ',' << y << "): (" + << w << "x" << h << ") -> (" + << col1 << ':' << col2 << ',' << row1 << ':' << row2 + << "). BB now [" << bbL << ':' << bbR << "), [" + << bbT << ':' << bbB << ")" << endl; +} + +/** + * Draws a `strut' on the master bitmap. This is essentially the same + * as the rule() method, except that it doesn't draw in + * any pixels. Its only effect is to make sure that the boundingbox + * includes at least the x-values [x-l,x+r-1], and the + * y-values [y-t+1,y+b]. That is, the area + * indicated by the strut is l+r pixels wide by + * t+b pixels deep. The parameters l, r, t, and + * b must all be non-negative. This implies that the call + * rule(x, y, w, h) has the same effect on the bounding + * box as rule(x, y, 0, w, h, 0). + * + * @param x the x-coordinate of the reference point of the strut + * @param y the y-coordinate of the reference point of the strut + * @param l bounding box must be leftwards of x-l + * @param r bounding box must be rightwards of x+r + * @param t bounding box must be above y-t + * @param b bounding box must be below y+b + * @throws BitmapError if this is called after method + * freeze(), or if one of l, r, t, b is negative + */ +void Bitmap::strut(const int x, const int y, + const int l, const int r, + const int t, const int b) + throw (BitmapError) +{ + if (frozen_) + throw BitmapError ("strut() called after freeze()"); + + if (l < 0 || r < 0 || t < 0 || b < 0) + throw BitmapError + ("Bitmap::strut all of l, r, t, b must be non-negative"); + + if (verbosity_ > normal) + cerr << "Bitmap::strut @ (" << x << ',' << y << "): (x-" + << l << ",x+" << r << ")/(y-" + << t << ",y+" << b << "):" + << "BB was [" << bbL << ':' << bbR << "), [" + << bbT << ':' << bbB << ")" << endl; + + // Mimic logic of rule() method: the pixels with coordinates + // [row1..row2-1] and [col1..col2-1] would be blackened by rule(). + int col1 = x-l; + int col2 = x+r; + int row1 = y-t+1; + int row2 = y+b+1; + + // the following is identical to rule... + + usesBitmapArea_(col1, row1, col2-1, row2-1); + + if (col1 < 0) col1 = 0; + if (col2 > W) col2 = W; + if (row1 < 0) row1 = 0; + if (row2 > H) row2 = H; + + // ...except that we don't actually draw anything + + if (col1 < bbL) bbL = col1; + if (col2 > bbR) bbR = col2; + if (row1 < bbT) bbT = row1; + if (row2 > bbB) bbB = row2; + + if (verbosity_ > normal) + cerr << "Bitmap:: ...BB now [" << bbL << ':' << bbR << "), [" + << bbT << ':' << bbB << ")" << endl; +} + +/** + * Marks a particular spot in the bitmap. This spot can be retrieved + * later using {@link #getMark}. The top-left pixel in the bitmap has + * mark coordinates (0,0). The input coordinates are not restricted + * to be on the bitmap. + * + * @param p a {@link DviFilePosition} representing the marked position + */ +void Bitmap::mark(DviFilePosition* p) +{ + // Recall that the origin of the bitmap, as well as the origin of + // the DviFilePosition, is in the top-left corner of the + // `page'/bitmap, which is not the same as the DVI origin. + if (mark_ != 0) + delete mark_; + + mark_ = p; + if (verbosity_ > normal) + cerr << "Bitmap::mark (" + << mark_->getX(DviFile::unit_pixels) << "," + << mark_->getY(DviFile::unit_pixels) << ")px" << endl; + return; +} + +/** + * Obtains the mark for this bitmap. + * + * @return a pointer to the mark information, or 0 if no mark has been + * registered. This points to static storage, which should not be + * deleted, and which may be overwritten. + * @see #mark + */ +DviFilePosition* Bitmap::getMark() +{ + if (mark_ == 0) + return 0; + + static DviFilePosition *reportMark; + if (reportMark != 0) + delete reportMark; + + // Report the mark position taking cropping into account + reportMark = mark_->copy(); + reportMark->shift(-cropL, -cropT, DviFile::unit_pixels); + + return reportMark; +} + +/** + * Freeze the bitmap and bounding box. This prevents any further + * changes to the bitmap by the methods paint(), + * rule() and strut(). Other methods in this + * class such as crop() and blur() call + * this method implicitly. + * + *

If method boundingBox() is called before this + * method, it is possible for it to report a size larger than the + * bitmap, if rules or bitmaps have been placed so that they overlap + * the bitmap's boundaries. The call to freeze + * normalises the bounding box so that this is no longer the case. + */ +void Bitmap::freeze() +{ + // Freeze the bitmap and bounding box, simply by setting the frozen_ flag + // to be true. At the same time, normalise the bounding box by requiring + // that (0 <= bbL < bbR <= W) and (0 <= bbT < bbB <= H). If, however, the + // bitmap is empty (according to empty()), then don't change anything. + // Code following this may therefore take these assertions to be valid + // as long as empty() is false. + // + // Code before this in this file should be called only when the bitmap + // is unfrozen, code afterwards freezes the bitmap if it is not + // frozen already. + if (frozen_) + return; // idempotent + + normalizeBB_(bbL, bbR, bbT, bbB); + + frozen_ = true; +} + +/** + * Normalizes the bounding box, so that it is no bigger than the bitmap. + */ +void Bitmap::normalizeBB_(int& tL, int& tR, int& tT, int& tB) +{ + if (!empty()) // do nothing if the bitmap is empty + { + if (verbosity_ > normal) + cerr << "Bitmap::normalizeBB: lr:[" << tL << "," << tR << "); tb:[" + << tT << ',' << tB << ")"; + + if (tL < 0) tL = 0; + if (tR > W) tR = W; + if (tT < 0) tT = 0; + if (tB > H) tB = H; + + if (verbosity_ > normal) + cerr << " --> lr:[" << tL << "," << tR << "); tb:[" + << tT << ',' << tB << ")" << endl; + + if ((tL >= tR) || (tT >= tB)) + // eh? this is really an assertion failure, I think + throw BitmapError + ("Bitmap::normalizeBB_: bitmap not empty, but bounds crossed (have you specified a silly crop?)"); + } +} + +/** + * Crops the bitmap. This applies the cropping specified in methods + * {@link #crop(Margin,int,bool)} and {@link #cropDefault}. + * + *

Freezes the bitmap as a side-effect. + */ +void Bitmap::crop() +{ + // Not idempotent, since scaleDown() requires to be able to + // re-call this function after scaling, which will happen only if + // cropped_ is true. + + if (scaled_) + throw BitmapError("crop() called after scaleDown()"); + + if (cropped_) + return; + + if (!frozen_) + freeze(); + + cropL = (cropMarginAbs[Left] ? cropMargin[Left] + : bbL - cropMargin[Left]); + cropR = (cropMarginAbs[Right] ? cropMargin[Right] + : bbR + cropMargin[Right]); + cropT = (cropMarginAbs[Top] ? cropMargin[Top] + : bbT - cropMargin[Top]); + cropB = (cropMarginAbs[Bottom] ? cropMargin[Bottom] + : bbB + cropMargin[Bottom]); + + // Ensure that the cropping hasn't pushed the margins out of the bitmap + normalizeBB_(cropL, cropR, cropT, cropB); + + if (verbosity_ > normal) { + cerr << "Bitmap::crop width [" << bbL << ',' << bbR + << "), height [" << bbT << ',' << bbB + << ") to width [" << cropL << ',' << cropR + << "), height [" << cropT << ',' << cropB + << ")" << endl; + } + + cropped_ = true; +} + +/** + * Specifies a crop. If the absolute flag is true, then + * set up a crop for the margin specified in spec: for + * the left and right margins, the crop in pixels is a + * distance from the left margin; for the top and bottom + * crops, it is from the top margin. If the + * absolute flag is false, then the distance in the + * pixels parameter is the distance `outward' of the + * eventual bounding-box, or at the edge of the bitmap, whichever + * comes first. + * + *

Since the implication of this is that a call + *

+ *   .crop(All, x, true);
+ * 
+ * would set the crop box to be zero size, this combination is forbidden. + * + * @param spec the margin the crop is being specified for + * @param pixels the size of the margin, or the position when + * absolute is true + * @param absolute if true, then the margin specified is an absolute + * position relative to the left or top margin as appropriate; if + * false, then it is relative to the eventual size and position of the + * bounding box + * @throws BitmapError if spec=All when + * absolute is true + */ +void Bitmap::crop(Margin spec, int pixels, bool absolute) + throw (BitmapError) +{ + if (spec == All) + { + if (absolute) + throw new BitmapError("Bitmap::crop(All,x,true): illegal call"); + + cropMargin[Left] = pixels; + cropMarginAbs[Left] = absolute; + cropMargin[Right] = pixels; + cropMarginAbs[Right] = absolute; + cropMargin[Top] = pixels; + cropMarginAbs[Top] = absolute; + cropMargin[Bottom] = pixels; + cropMarginAbs[Bottom] = absolute; + } + else + { + assert (spec >= Left && spec <= Bottom); + cropMargin[spec] = pixels; + cropMarginAbs[spec] = absolute; + } +} + +/** + * Specifies a default crop. This is exactly the same as {@link + * #crop(Margin,int,bool)}, except that it specifies this for all the + * bitmaps subsequently created by this class. + * + * @param spec the margin the crop is being specified for + * @param pixels the size of the margin, or the position when + * absolute is true + * @param absolute if true, then the margin specified is an absolute + * position relative to the left or top margin as appropriate; if + * false, then it is relative to the eventual size and position of the + * bounding box + * @throws BitmapError if spec=All when + * absolute is true + * @see #crop(Margin,int,bool) + */ +void Bitmap::cropDefault (Margin spec, int pixels, bool absolute) + throw (BitmapError) +{ + if (spec == All) + { + if (absolute) + throw new BitmapError + ("Bitmap::cropDefault(All,x,true): illegal call"); + + cropMarginDefault[Left] = pixels; + cropMarginAbsDefault[Left] = absolute; + cropMarginDefault[Right] = pixels; + cropMarginAbsDefault[Right] = absolute; + cropMarginDefault[Top] = pixels; + cropMarginAbsDefault[Top] = absolute; + cropMarginDefault[Bottom] = pixels; + cropMarginAbsDefault[Bottom] = absolute; + } + else + { + assert (spec >= Left && spec <= Bottom); + cropMarginDefault[spec] = pixels; + cropMarginAbsDefault[spec] = absolute; + } +} + +/** + * Does the bitmap overlap its canvas? This can only be true before a + * (implicit or explicit) call to {@link #freeze}, since that + * normalizes the bounding box variables. + * + * @return true if the bitmap overlaps its canvas; always false after + * any call to freeze() + */ +bool Bitmap::overlaps () + const +{ + if (verbosity_ > normal) { + bool res = (bbL < 0 || bbR > W || bbT < 0 || bbB > H); + cerr << "Bitmap::overlaps [" << bbL << "," << bbR + << "," << bbT << "," << bbB + << "] vs [0," << W << ",0," << H << "] ==> " << res << endl; + return res; + } else { + return (bbL < 0 || bbR > W || bbT < 0 || bbB > H); + } +} + +/** + * Obtain a bounding box for the current bitmap. This returns a + * four-element array consisting of, in order, + *
    + *
  • [0] = the coordinate of the leftmost blackened pixel, + *
  • [1] = the coordinate of the topmost blackened pixel, + *
  • [2] = one more than the coordinate of the rightmost blackened pixel, and + *
  • [3] = one more than the coordinate of the bottommost blackened pixel. + *
+ * Thus [2]-[0] is the number of pixels which the + * blackened area occupies in the horizontal direction. Note that + * `blackened pixels' here includes those notionally blackened by the + * {@link #strut}() method. If the bitmap has been + * cropped, this bounding box reflects the crop margins. + * + *

The returned array occupies + * static storage, and is always current as of the last time this + * method was called. + * + *

The methods {@link #getWidth}() and {@link + * #getHeight}() return the size of the bitmap irrespective of + * the bounding box and any cropping. + * + *

It is possible for the bounding-box to be bigger than the + * bitmap, if rules or bitmaps have been painted on the bitmap in such + * a way that they overlap the boundaries of the bitmap, and + * if it is called before an explicit or implicit call to + * {@link #freeze}(). This can also be detected by a call to + * {@link #overlaps}() before any call to freeze(). + * It is never bigger than the bitmap after the bitmap is frozen. + * + *

Note that the order of the four dimensions is not that of + * the Postscript BoundingBox, which is (llx, lly, urx, ury) + * rather than here, effectively, (ulx, uly, lrx, lry). This is + * because the position of the upper-left corner (ulx, uly) is + * the natural TeX reference point. + * + * @return the position of the bitmap bounding-box, in the order + * (ulx, uly, lrx, lry) + */ +int *Bitmap::boundingBox() +{ + if (cropped_) { + BB[0] = cropL; + BB[1] = cropT; + BB[2] = cropR; + BB[3] = cropB; + } else { + BB[0]=bbL; + BB[1]=bbT; + BB[2]=bbR; + BB[3]=bbB; + } + return &BB[0]; +} + +/** + * Makes a very simple-minded attempt to antialias the bitmap by + * blurring it. Opening the DVI file with a magnification setting, + * and then calling {@link #scaleDown} will generally produce a much + * better effect. + * + *

Freezes the bitmap as a side-effect. + */ +void Bitmap::blur () +{ + if (!frozen_) + freeze(); + + if (empty()) // nothing there - nothing to do + return; // ...silently + + Byte *newB = new Byte[W*H]; + STD::memset ((void*)newB, 0, W*H); + + int newbpp = (bpp_ < 2 ? 2 : bpp_); + Byte new_max_colour = static_cast((1<((B[(row-1)*W+(col-1)] + B[(row-1)*W+(col+1)] + + B[(row+1)*W+(col-1)] + B[(row+1)*W+(col+1)] + + B[row*W+col]*4) + / 8.0 // weighting + * scale + + 0.5); + */ + = static_cast(( B[row*W+col-1] + B[row*W+col+1] + + B[(row+1)*W+col] + B[(row-1)*W+col] + + B[row*W+col]*2) + / 6.0 // weighting + * scale + + 0.5); + delete[] B; + bpp_ = newbpp; + max_colour_ = new_max_colour; + B = newB; +} + +/** + * Scales down the bitmap by a numerical factor. The resulting bitmap + * has a linear dimension smaller than the original by the given + * factor. The pixels in the resulting bitmap are resampled so that + * this gives a basic anti-aliasing effect. + * + *

We throw an exception if you try to scale down an empty bitmap, + * simply on the grounds that this is probably an error, and you want + * to know about it. + * + *

Freezes the bitmap as a side-effect. + * + * @param factor the scaling factor, in the range 2..8 + * @throws BitmapError if the scaling factor is outside the range + * 2..8, or if the bitmap is empty + */ +void Bitmap::scaleDown (const int factor) + throw (BitmapError) +{ + if (!frozen_) + freeze(); + + if (factor < 2 || factor > 8) // shome mistake, shurely + throw BitmapError ("out-of-range scale factor - must be in 2..8"); + + if (empty()) // nothing there - nothing to do + // Should we instead silently decrease the size of the bitmap? + // No - this is surely an error on the user's part. + throw BitmapError ("attempt to scale an empty bitmap"); + + // We don't create a new bitmap. Instead, we pack the scaled-down + // bitmap into the small-index corner of the original, and reset + // the bounding-box to respect this. Because of this, we don't + // have to clear the old bitmap surrounding it, since this is now ignored. + // + // The original bounding box may not have been an exact multiple + // of the target one. Take careful account of the `extra' rows + // and columns on the right/bottom. + + assert(bbL >= 0 && bbT >= 0); + assert(bbR >= bbL && bbB >= bbT); + + // oldX represents the boundaries of the region to be scaled down, + // after cropping; that is, it will typically, but not + // necessarily, be larger than the bounding-box region. + int oldL, oldR, oldT, oldB; + if (cropped_) { + oldL = cropL; + oldR = cropR; + oldT = cropT; + oldB = cropB; + } else { + oldL = 0; + oldR = W; + oldT = 0; + oldB = H; + } + + // newX represents the boundaries of the region this will be + // mapped into, which is moved into the top-left corner of the + // bitmap. This maps to a region in the original bitmap which is + // an integer number of factor*factor squares in size. It is no + // smaller than the bb region, but might be up to `factor' pixels + // larger in horizontal and vertical extent. The non-square + // extent of these rightmost and bottommost border regions is + // handled below, in the calculations of rowspan and colspan. + int newL = 0; + int newR = (oldR-oldL + (factor-1))/factor; + int newT = 0; + int newB = (oldB-oldT + (factor-1))/factor; + + assert(newL <= newR); + assert(newT <= newB); + + // Preserve these new crop sizes + if (cropped_) { + cropL = newL; + cropR = newR; + cropT = newT; + cropB = newB; + } + + // Make sure there are at least 6 bits-per-pixel, to accomodate 64 + // (=8*8) levels of grey. This is crude, but acceptable as a first-go + // heuristic + int newbpp = (bpp_ < 6 ? 6 : bpp_); + Byte new_max_colour = static_cast((1< oldB) { + // we would overlap the edge, so shorten the span + rowspan = oldB - y; + } + for (int col1=newL; col1 oldR) { + colspan = oldR - x; + } + assert(oldL <= x && x+colspan <= oldR); + assert(oldT <= y && y+rowspan <= oldB); + for (int row2=y; row2(tot*scale); +#else + B[row1*W+col1] + = static_cast(tot*new_max_colour + /(double)(count*max_colour_)); +#endif + } + } + + if (mark_ != 0) { + // Scale the mark position, too. This is easy, since we've + // documented that the top-left pixel has coordinates (0,0) + mark_->shift(-oldL, -oldT, DviFile::unit_pixels); + mark_->scale(1.0/factor); + /* + DviFilePosition fixedPoint + = DviFilePosition(mark_, 0, 0, DviFile::unit_pixels); + mark_->scale(1.0/factor, fixedPoint); + */ + } + + bbL = newL + static_cast((bbL-oldL+0.5)/factor); + bbR = newL + static_cast((bbR-oldL+0.5)/factor); + bbT = newT + static_cast((bbT-oldT+0.5)/factor); + bbB = newT + static_cast((bbB-oldT+0.5)/factor); + + bpp_ = newbpp; + max_colour_ = new_max_colour; + + scaled_ = true; + + if (verbosity_ > normal) + cerr << "Bitmap::scaleDown: factor=" << factor + << ". BB now [" << bbL << ':' << bbR << "), [" + << bbT << ':' << bbB + << "); bpp=" << bpp_ << ", max_colour=" << max_colour_ + << endl; +} + +/** + * Writes the bitmap out to the specified file. The + * format parameter specifies the format of this file, + * and should be one of the bitmap types listed in the sequence + * starting with {@link BitmapImage#firstBitmapImageFormat}; if this + * is not available, we try writing out in the default format, and if + * that fails in turn (something is clearly badly wrong) we throw an + * error. + * + *

Freezes the bitmap as a side-effect. + * + * @param filename the name of the output filename + * @param format one of the format names known to class + * BitmapImage + * @throws BitmapError if we cannot write out a bitmap even in the + * default format + * @see BitmapImage + */ +void Bitmap::write(const string filename, const string format) + throw (BitmapError) +{ + if (!frozen_) + freeze(); + + if (verbosity_ > normal) + cerr << "Bitmap::write: " + << " cropped=" << cropped_ + << " scaled_=" << scaled_ + << " bbL=" << bbL + << " bbR=" << bbR + << " bbT=" << bbT + << " bbB=" << bbB + << " cropL=" << cropL + << " cropR=" << cropR + << " cropT=" << cropT + << " cropB=" << cropB + << " W="<= normal) + cerr << "Bitmap: can't create image with format " + << format + << ". Trying format " + << deffmt + << " instead" << endl; + bi = BitmapImage::newBitmapImage(deffmt, hsize, vsize, bpp_); + if (bi == 0) + throw BitmapError("Bitmap: can't create image with default format"); + } + if (cropped_) { + // Do a sanity-check on cropL..cropB, to make sure that we won't + // bust the bounds of B[] when writing out. + assert (cropT>=0 && cropB<=H && cropL>=0 && cropLsetBitmapRow(*it); + } else { + bi->setBitmap (B); + } + if (verbosity_ > normal) + cerr << "Bitmap: transparent=" << transparent_ << endl; + bi->setTransparent (transparent_); + if (customRGB_) + { + if (verbosity_ > normal) + cerr << "Bitmap: custom RGB: " + << static_cast(fg_.red) << ',' + << static_cast(fg_.green) << ',' + << static_cast(fg_.blue) << '/' + << static_cast(bg_.red) << ',' + << static_cast(bg_.green) << ',' + << static_cast(bg_.blue) << endl; + bi->setRGB (true, &fg_); + bi->setRGB (false, &bg_); + } + string fileext = bi->fileExtension(); + string outfilename = filename; + if (fileext.length() != 0) + { + size_t extlen = fileext.length(); + if (extlen > outfilename.length() || + outfilename.substr(outfilename.length()-extlen, extlen) != fileext) + outfilename += '.' + fileext; + } + bi->write (outfilename); + + if (logBitmapPrefix_ != 0) { + cout << logBitmapPrefix_ << outfilename + << ' ' << hsize << ' ' << vsize; + DviFilePosition *m = getMark(); + if (m != 0) { + // We add one to the reported y-coordinate of the mark. This + // appears ill-motivated, but it's ultimately caused by + // the observation that, though the underlying coordinate + // system has the y-axis pointing downwards, things like + // characters and rules (and to some extent struts) are + // positioned with reference to their bottom-left corner, + // rather than their top-left, and what's actually + // positioned at the specified position is the _centre_ of the + // bottom-left pixel, rather than, really, the corner. + // This has the effect that everything ends up one pixel + // down from where one feels it ought to be. However, + // this doesn't matter, since we don't really much care + // about the absolute position on the bitmap. This + // apparently gratuitous +1 is the clearest + // manifestation of the asymmetry, but adding it means + // that if, for example, you have a page with only a 10x10 + // rule on it, and the mark immediately afterwards, the + // mark is reported as being at (x=10,y=10), rather than (10,9). + // The point of this mark mechanism is more to give a + // consistent and intelligible reference point, than that + // it be tied to any particular DVI structure. + // + // Having said that, it might be worth taking a close look + // at the DVI standard, and at least noting where rules are + // positioned according to that. + cout << ' ' << static_cast(m->getX(DviFile::unit_pixels)+0.5) + << ' ' << static_cast(m->getY(DviFile::unit_pixels)+1.5); + } + cout << endl; + } + + delete bi; +} + +/** + * Sets the foreground or background colour. + * + * @param fg if true, sets the foreground colour; if false, the background + * @param rgb the colour the ground is set to + */ +void Bitmap::setRGB (const bool fg, const BitmapColour* rgb) { + if (verbosity_ > normal) + cerr << "Bitmap::setRGB: " + << " fg=" << fg + << " RGB=" + << static_cast(rgb->red) << ',' + << static_cast(rgb->green) << ',' + << static_cast(rgb->blue) << endl; + if (fg) + { + fg_.red = rgb->red; + fg_.green = rgb->green; + fg_.blue = rgb->blue; + } else { + bg_.red = rgb->red; + bg_.green = rgb->green; + bg_.blue = rgb->blue; + } + customRGB_ = true; +} + +/** + * Sets the default foreground or background colours. + * This is just like setRGB, except that it applies to + * all bitmaps subsequently created by this class. + * + * @param fg if true, sets the foreground colour; if false, the background + * @param rgb the colour the ground is set to + */ +void Bitmap::setDefaultRGB (const bool fg, const BitmapColour* rgb) { + if (verbosity_ > normal) + cerr << "Bitmap::setDefaultRGB: " + << " fg=" << fg + << " RGB=" + << static_cast(rgb->red) << ',' + << static_cast(rgb->green) << ',' + << static_cast(rgb->blue) << endl; + if (fg) + { + def_fg_.red = rgb->red; + def_fg_.green = rgb->green; + def_fg_.blue = rgb->blue; + } else { + def_bg_.red = rgb->red; + def_bg_.green = rgb->green; + def_bg_.blue = rgb->blue; + } + def_customRGB_ = true; +} + + +/** + * Returns the beginning of a sequence of bitmap rows. + *

Freezes the bitmap as a side-effect. + */ +Bitmap::const_iterator Bitmap::begin() +{ + if (!frozen_) + freeze(); + runningIterator_.init(B, + (cropped_ ? cropL : 0), + (cropped_ ? cropT : 0), + W, + (cropped_ ? cropB-cropT : H)); + return runningIterator_; +} +/** + * Returns the end of a sequence of bitmap rows. + */ +Bitmap::const_iterator Bitmap::end() + const +{ + if (Bitmap::endIterator_.rowNumber_ == 0) // initialisation + Bitmap::endIterator_.rowNumber_ = -1; + return Bitmap::endIterator_; +} +Bitmap::const_iterator::const_iterator() +{ + // empty +} +Bitmap::const_iterator::~const_iterator() +{ + // empty +} +void Bitmap::const_iterator::init(Byte* b, + int startx, int starty, + int width, int nrows) +{ + b_ = b; + rowNumber_ = starty; + lastRow_ = starty + nrows; + rowLength_ = width; + startColumn_ = startx; +} +/** + * Returns the current member of the set of rows returned by the + * iterator. This returns a pointer to an array of + * Byte, with elements [0..W-1] being + * guaranteed to be valid, where W is the width of the + * bitmap. If the bitmap is uncropped, this is the total width of the + * bitmap as returned by method {@link #getWidth}; if cropped, the + * width is the difference of the [2] and [0] elements of the array + * returned by {@link #boundingBox}. + * + * @return pointer to an array of Byte + * @throws DviError if the iterator is dereferenced after it has come + * to the end + */ +Byte* Bitmap::const_iterator::operator*() + throw (DviError) +{ + if (rowNumber_ < 0 || rowNumber_ >= lastRow_) { + throw new DviError("Out-of-range dereference of const_iterator"); + } + return &b_[rowNumber_ * rowLength_ + startColumn_]; +} +/** + * Increments the iterator. If the bitmap is uncropped, all the rows + * in the bitmap will eventually be iterator over, namely the number + * of rows returned by method {@link #getHeight}; if it is cropped, + * the number of rows returned will be the difference between the [3] + * and [1] elements of the {@link #boundingBox} array. + * + * @return the iterator + * @throws DviError if the iterator is incremented after it has come + * to the end + */ +Bitmap::const_iterator& Bitmap::const_iterator::operator++() + throw (DviError) +{ + if (rowNumber_ < 0 || rowNumber_ >= lastRow_) { + throw new DviError("Out-of-range increment of const_iterator"); + } + ++rowNumber_; + if (rowNumber_ == lastRow_) + rowNumber_ = -1; // matches endIterator_ + return *this; +} +bool Bitmap::const_iterator::operator==(const Bitmap::const_iterator& it) + const +{ + return (rowNumber_ == it.rowNumber_); +} +bool Bitmap::const_iterator::operator!=(const Bitmap::const_iterator& it) + const +{ + return rowNumber_ != it.rowNumber_; +} + -- cgit v1.2.3