/* 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_; }