diff --git a/halftoning/CommonTypedefs.h b/halftoning/CommonTypedefs.h new file mode 100644 index 0000000..87e9ce4 --- /dev/null +++ b/halftoning/CommonTypedefs.h @@ -0,0 +1,42 @@ +// -*- C++ -*- +// $Id: CommonTypedefs.h 14976 2011-04-26 15:24:48Z aleksandr $ + +// DYMO LabelWriter Drivers +// Copyright (C) 2008 Sanford L.P. + +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +#ifndef haf1c6298_a459_4417_83c1_ed084705400a +#define haf1c6298_a459_4417_83c1_ed084705400a + +#include + +//namespace dymo +namespace DymoPrinterDriver +{ + +typedef unsigned char byte; +typedef unsigned int dword; +typedef std::vector buffer_t; + +} // namespace + + +#endif + +/* + * End of "$Id: CommonTypedefs.h 14976 2011-04-26 15:24:48Z aleksandr $". + */ diff --git a/halftoning/ErrorDiffusionHalftoning.cpp b/halftoning/ErrorDiffusionHalftoning.cpp new file mode 100644 index 0000000..273ced7 --- /dev/null +++ b/halftoning/ErrorDiffusionHalftoning.cpp @@ -0,0 +1,177 @@ +// -*- C++ -*- +// $Id: ErrorDiffusionHalftoning.cpp 4759 2008-06-19 19:02:27Z vbuzuev $ + +// DYMO LabelWriter Drivers +// Copyright (C) 2008 Sanford L.P. + +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include "ErrorDiffusionHalftoning.h" +#include + +//namespace dymo +namespace DymoPrinterDriver +{ + + +CErrorDiffusionHalftoning::CErrorDiffusionHalftoning(image_t InputImageType, image_t OutputImageType, bool UsePrinterColorSpace): + CHalftoneFilter(InputImageType, OutputImageType), ImageWidth_(0), Errors_(), GrayLine_(), UsePrinterColorSpace_(UsePrinterColorSpace) +{ + if (GetOutputImageType() != itBW) + throw EHalftoneError(EHalftoneError::heUnsupportedImageType); +} + +CErrorDiffusionHalftoning::~CErrorDiffusionHalftoning() +{ +} + + +bool +CErrorDiffusionHalftoning::IsProcessLineSupported() +{ + return true; +} + +void +CErrorDiffusionHalftoning::ProcessLine( + const buffer_t& InputLine, buffer_t& OutputLine) +{ + int pixelValue = 0; + int error = 0; + size_t i = 0; + + // set image width + if (!ImageWidth_) + ImageWidth_ = CalcImageWidth(InputLine); + + // check buffer size + OutputLine.resize(CalcOutputBufferSize(ImageWidth_)); + std::fill(OutputLine.begin(), OutputLine.end(), byte(0)); + + // initialize halftone errors array and line buffer + if (Errors_.size() == 0) + Errors_.resize(ImageWidth_, 0); + + if (GrayLine_.size() == 0) + GrayLine_.resize(ImageWidth_, 0); + + // convert from RGB to GrayScale + for (i = 0; i < ImageWidth_; ++i) + { + byte R, G, B; + ExtractRGB(InputLine, i, R, G, B); + GrayLine_[i] = RGBToGrayScale(R, G, B); + } + + // apply errors from prev line + for (i = 0; i < ImageWidth_; ++i) + { + //fprintf(stderr, "%i ", Errors_[i]); + // only if not black and white + if ((GrayLine_[i] != 0) && (GrayLine_[i] != 255)) + { + if (Errors_[i] + GrayLine_[i] >= 255) + GrayLine_[i] = 255; + else + if (Errors_[i] + GrayLine_[i] <= 0) + GrayLine_[i] = 0; + else + GrayLine_[i] += Errors_[i]; + } + + Errors_[i] = 0; + } + + + // compute output pixels and new errors + for (i = 0; i < ImageWidth_; ++i) + { + pixelValue = GrayLine_[i] >= 128; + error = GrayLine_[i] - pixelValue * 255; + + if (UsePrinterColorSpace_) + SetPixelBW(OutputLine, i, !pixelValue); + else + SetPixelBW(OutputLine, i, pixelValue); + + + // disribute error + if (i > 0) + Errors_[i - 1] += (error * 3) / 16; + + Errors_[i] += (error * 5) / 16; + + if (i < ImageWidth_ - 1) + { + Errors_[i + 1] += (error * 1) / 16; + + if ((GrayLine_[i + 1] != 0) && (GrayLine_[i + 1] != 255)) + { + error = (error * 7) / 16; + + if (error + GrayLine_[i + 1] >= 255) + GrayLine_[i + 1] = 255; + else + if (error + GrayLine_[i + 1] <= 0) + GrayLine_[i + 1] = 0; + else + GrayLine_[i + 1] += error; + } + } + + } // for all pixels +} + +void +CErrorDiffusionHalftoning::ProcessImage( + const void* ImageData, size_t ImageWidth, size_t ImageHeight, size_t LineDelta, std::vector& OutputImage) +{ + OutputImage.clear(); + + buffer_t InputLine; + size_t BufferSize = CalcBufferSize(ImageWidth); + InputLine.resize(BufferSize, 0); + + for (size_t i = 0; i < ImageHeight; ++i) + { + InputLine.assign( + (byte*)ImageData + LineDelta*i, + (byte*)ImageData + LineDelta*i + BufferSize); + + OutputImage.push_back(buffer_t()); + ProcessLine(InputLine, OutputImage[OutputImage.size() - 1]); + } +} + +void +CErrorDiffusionHalftoning::ProcessImage(const std::vector& InputImage, std::vector& OutputImage) +{ + OutputImage.clear(); + + buffer_t OutputLine; + + for (std::vector::const_iterator i = InputImage.begin(); i < InputImage.end(); ++i) + { + ProcessLine(*i, OutputLine); + OutputImage.push_back(OutputLine); + } +} + + +} // namespace + +/* + * End of "$Id: ErrorDiffusionHalftoning.cpp 4759 2008-06-19 19:02:27Z vbuzuev $". + */ diff --git a/halftoning/ErrorDiffusionHalftoning.h b/halftoning/ErrorDiffusionHalftoning.h new file mode 100644 index 0000000..e03a3af --- /dev/null +++ b/halftoning/ErrorDiffusionHalftoning.h @@ -0,0 +1,60 @@ +// -*- C++ -*- +// $Id: ErrorDiffusionHalftoning.h 4759 2008-06-19 19:02:27Z vbuzuev $ + +// DYMO LabelWriter Drivers +// Copyright (C) 2008 Sanford L.P. + +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +#ifndef h70F89562_C051_4c11_80E4_E5BE22A3C400 +#define h70F89562_C051_4c11_80E4_E5BE22A3C400 + +#include "Halftoning.h" + +//namespace dymo +namespace DymoPrinterDriver +{ + +class CErrorDiffusionHalftoning: public CHalftoneFilter +{ +public: + CErrorDiffusionHalftoning(image_t InputImageType, image_t OutputImageType, bool UsePrinterColorSpace = true); + virtual ~CErrorDiffusionHalftoning(); + + virtual bool IsProcessLineSupported(); + + virtual void ProcessLine(const buffer_t& InputLine, buffer_t& OutputLine); + virtual void ProcessImage(const void* ImageData, size_t ImageWidth, size_t ImageHeight, size_t LineDelta, std::vector& OutputImage); + virtual void ProcessImage(const std::vector& InputImage, std::vector& OutputImage); + +protected: + size_t GetImageWidth(); + +private: + + size_t ImageWidth_; // image width in pixels + std::vector Errors_; // errors buffer + std::vector GrayLine_; // current line in gray scale color + bool UsePrinterColorSpace_; // if true then use 1 as black, 0 - as white; otherwise 0 is black 1 - white +}; + +}; // namespace + +#endif + +/* + * End of "$Id: ErrorDiffusionHalftoning.h 4759 2008-06-19 19:02:27Z vbuzuev $". + */ diff --git a/halftoning/HalftoneHelper.h b/halftoning/HalftoneHelper.h new file mode 100644 index 0000000..6852ed5 --- /dev/null +++ b/halftoning/HalftoneHelper.h @@ -0,0 +1,31 @@ +// -*- C++ -*- + +// HalftoneHelper.h - C bridge header for C++ classes + +// Copyright (C) 2023 VintagePC + +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +extern "C" +{ + extern int do_halftone_err_diff(unsigned char* buffer, int bufLen); + + extern void nll_clear_buffers(); + extern void nll_add_line(unsigned char* buffer, int bufLen); + extern void nll_process(); + extern int nll_get_next_line(unsigned char* buffer, int bufLen); + +} \ No newline at end of file diff --git a/halftoning/Halftoning.cpp b/halftoning/Halftoning.cpp new file mode 100644 index 0000000..49175bf --- /dev/null +++ b/halftoning/Halftoning.cpp @@ -0,0 +1,251 @@ +// -*- C++ -*- +// $Id: Halftoning.cpp 4759 2008-06-19 19:02:27Z vbuzuev $ + +// DYMO LabelWriter Drivers +// Copyright (C) 2008 Sanford L.P. +// C linkages added 2023 by VintagePC + +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include "Halftoning.h" +#include "HalftoneHelper.h" +#include "ErrorDiffusionHalftoning.h" +#include "NonLinearLaplacianHalftoning.h" +#include +#include +#include +#include + +//using namespace std; + +//namespace dymo +namespace DymoPrinterDriver +{ + +CHalftoneFilter::CHalftoneFilter(image_t InputImageType, image_t OutputImageType): + InputImageType_(InputImageType), OutputImageType_(OutputImageType) +{ +} + +CHalftoneFilter::~CHalftoneFilter() +{ +} + +CHalftoneFilter::image_t +CHalftoneFilter::GetInputImageType() +{ + return InputImageType_; +} + +CHalftoneFilter::image_t +CHalftoneFilter::GetOutputImageType() +{ + return OutputImageType_; +} + +byte +CHalftoneFilter::RGBToGrayScale(byte R, byte G, byte B) +{ + // white should remain white + if ((R == 255) && (G == 255) && (B == 255)) + return 255; + else if ((R == 0) && (G == 0) && (B == 0)) + return 0; + else + { + int r = 0 + ((int(R) * 299) / 1000) + ((int(G) * 587) / 1000) + ((int(B) * 114) / 1000); + if (r > 255) + return 255; + return byte(r); + } +} + +// set pixel pixelNo to +// pixelValue (0 - white, 1 - black) +void +CHalftoneFilter::SetPixelBW(buffer_t& buf, int pixelNo, int pixelValue) +{ + if (pixelValue) + buf[pixelNo / 8] |= (1 << (7 - pixelNo % 8)); + else + buf[pixelNo / 8] &= ~(1 << (7 - pixelNo % 8)); +} + +void +CHalftoneFilter::ExtractRGB(const buffer_t& InputLine, int PixelNo, byte& R, byte& G, byte& B) +{ + switch (InputImageType_) + { + case itXRGB: + R = InputLine[4*PixelNo + 1]; + G = InputLine[4*PixelNo + 2]; + B = InputLine[4*PixelNo + 3]; + break; + case itRGB: + R = InputLine[3*PixelNo + 0]; + G = InputLine[3*PixelNo + 1]; + B = InputLine[3*PixelNo + 2]; + break; + default: + assert(0); + } +} + +size_t +CHalftoneFilter::CalcImageWidth(const buffer_t& InputLine) +{ + switch (InputImageType_) + { + case itXRGB: + return InputLine.size() / 4; + case itRGB: + return InputLine.size() / 3; + default: + assert(0); + } + + return 0; // for MSVC compiler +} + + +size_t +CHalftoneFilter::CalcBufferSize(size_t ImageWidth) +{ + switch (InputImageType_) + { + case itXRGB: + return ImageWidth * 4; + case itRGB: + return ImageWidth * 3; + default: + assert(0); + } + + return 0; // for MSVC compiler +} + +size_t +CHalftoneFilter::CalcOutputBufferSize(size_t ImageWidth) +{ + switch (OutputImageType_) + { + case itBW: + if (ImageWidth % 8 == 0) + return ImageWidth / 8; + else + return ImageWidth / 8 + 1; + default: + assert(0); + } + + return 0; // for MSVC compiler +} + +int +CHalftoneFilter::ExtractRGB(const buffer_t& InputLine, int PixelNo) +{ + switch (InputImageType_) + { + case itXRGB: + return + (int(InputLine[4*PixelNo + 1]) << 16) + || (int(InputLine[4*PixelNo + 2]) << 8) + || (InputLine[4*PixelNo + 3] ); + case itRGB: + return + (int(InputLine[3*PixelNo + 0]) << 16) + || (int(InputLine[3*PixelNo + 1]) << 8) + || (InputLine[3*PixelNo + 2] ); + default: + assert(0); + } + + return 0; // for MSVC compiler +} + +///////////////////////////////////////////////////////////////////////// +// EHalftoneError +///////////////////////////////////////////////////////////////////////// + +EHalftoneError::EHalftoneError(error_t ErrorCode): ErrorCode_(ErrorCode) +{ +} + +EHalftoneError::error_t +EHalftoneError::GetErrorCode() +{ + return ErrorCode_; +} + + +} // namespace + +/* + * End of "$Id: Halftoning.cpp 4759 2008-06-19 19:02:27Z vbuzuev $". + */ + + // C linkages for C++ classes: + +using ErrDiff = DymoPrinterDriver::CErrorDiffusionHalftoning; +using NLL = DymoPrinterDriver::CNLLHalftoning; +using Filter = DymoPrinterDriver::CHalftoneFilter; +using DymoPrinterDriver::buffer_t; +using std::vector; +extern "C" +{ + extern int do_halftone_err_diff(unsigned char* buffer, int bufLen) + { + ErrDiff H(Filter::itRGB, Filter::itBW); + buffer_t input(buffer, buffer + bufLen), output; + H.ProcessLine(input, output); + assert(output.size() <= bufLen); + memcpy(buffer, output.data() , output.size()); + return output.size(); + } + + std::vector input_image, output_image; + + extern void nll_clear_buffers() + { + input_image.clear(); + output_image.clear(); + } + + extern void nll_add_line(unsigned char* buffer, int bufLen) + { + buffer_t line(buffer, buffer+bufLen); + input_image.push_back(line); + } + + extern void nll_process() + { + NLL H(64, Filter::itRGB, Filter::itBW); + H.ProcessImage(input_image,output_image); + } + + extern int nll_get_next_line(unsigned char* buffer, int bufLen) + { + if (output_image.size()==0) + { + return 0; + } + buffer_t line = output_image.front(); + assert(line.size() <= bufLen); + memcpy(buffer, line.data(), line.size()); + output_image.erase(output_image.begin()); + return line.size(); + } + +} \ No newline at end of file diff --git a/halftoning/Halftoning.h b/halftoning/Halftoning.h new file mode 100644 index 0000000..4d91291 --- /dev/null +++ b/halftoning/Halftoning.h @@ -0,0 +1,106 @@ +// -*- C++ -*- +// $Id: Halftoning.h 15960 2011-09-02 14:42:28Z pineichen $ + +// DYMO LabelWriter Drivers +// Copyright (C) 2008 Sanford L.P. + +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +#ifndef h4D098F6A_47C6_4e9d_BD74_2DC6034F8EEF +#define h4D098F6A_47C6_4e9d_BD74_2DC6034F8EEF + +#include +#include "CommonTypedefs.h" + +//namespace dymo +namespace DymoPrinterDriver +{ + +class CHalftoneFilter +{ +public: + // image format + typedef enum + { + itBW, // Black and White + itXRGB, // four bytes per pixel, 8 bits per color, msb is not used (default on MacOSX) + itRGB, // three bytes per pixel, 8 bits per color (default on CUPS) + } image_t; + + typedef std::vector image_buffer_t; + + CHalftoneFilter(image_t InputImageType, image_t OutputImageType); + virtual ~CHalftoneFilter(); + + + // line-by-line interface + virtual bool IsProcessLineSupported() = 0; + virtual void ProcessLine(const buffer_t& InputLine, buffer_t& OutputLine) = 0; + + // full-image-at-once interface + virtual void ProcessImage(const void* ImageData, size_t ImageWidth, size_t ImageHeight, size_t LineDelta, std::vector& OutputImage) = 0; + virtual void ProcessImage(const image_buffer_t& InputImage, image_buffer_t& OutputImage) = 0; + + image_t GetInputImageType(); + image_t GetOutputImageType(); + + // convert RGB value to Gray Scale + byte RGBToGrayScale(byte R, byte G, byte B); + + // pixelValue (0 - white, 1 - black) + void SetPixelBW(buffer_t& buf, int pixelNo, int pixelValue); + + // based on inputImageType extract color component of current pixel + void ExtractRGB(const buffer_t& InputLine, int PixelNo, byte& R, byte& G, byte& B); + // same as previous but return colors as packed integer value + int ExtractRGB(const buffer_t& InputLine, int PixelNo); + + // return imageWidth based on inputImageType and input line data + size_t CalcImageWidth(const buffer_t& InputLine); + // return buffer size needed to store an input line based on inputImageType + size_t CalcBufferSize(size_t ImageWidth); + // calc output buffer size + size_t CalcOutputBufferSize(size_t ImageWidth); + +private: + image_t InputImageType_; + image_t OutputImageType_; +}; + + +class EHalftoneError +{ +public: + typedef enum + { + heUnsupportedImageType = 1, + } error_t; + + EHalftoneError(error_t ErrorCode); + + error_t GetErrorCode(); + +private: + error_t ErrorCode_; +}; + +} + +#endif + +/* + * End of "$Id: Halftoning.h 15960 2011-09-02 14:42:28Z pineichen $". + */ diff --git a/halftoning/NonLinearLaplacianHalftoning.cpp b/halftoning/NonLinearLaplacianHalftoning.cpp new file mode 100644 index 0000000..0eac298 --- /dev/null +++ b/halftoning/NonLinearLaplacianHalftoning.cpp @@ -0,0 +1,646 @@ +// -*- C++ -*- +// $Id: NonLinearLaplacianHalftoning.cpp 4759 2008-06-19 19:02:27Z vbuzuev $ + +// DYMO LabelWriter Drivers +// Copyright (C) 2008 Sanford L.P. + +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include "NonLinearLaplacianHalftoning.h" +#include + +//namespace dymo +namespace DymoPrinterDriver +{ + +// helper class defines image 'block' of 18 pixels +class CNLLBlock +{ +public: + // create a block from image with coordinates of pixel #1 at (x1, y1) + CNLLBlock(CNLLHalftoning& Parent, const CHalftoneFilter::image_buffer_t& Image, int x1, int y1, CHalftoneFilter::image_buffer_t& OutputImage); + + // return true if at least on pixels of the block is insize the image + bool IsInImage(); + + // fill block information + void FillBlock(); + void OutputBlock(); + +private: + // return intense value of the block - original number of pixels to draw in 'black' + size_t GetBlockIntenseValue(); + + // fill info for one of 18 pixels + // PixelNo - ordinal number of the pixel in the block + // (x, y) - coords of the pixel in original image + void FillPixel(size_t PixelNo, int x, int y); + + // Split class 1 pixels to class 2 and class 5 to class 4 + void ReduceClasses(); + void ReduceClasses(size_t ClassFrom, size_t ClassTo); + + // return Laplacian value for pixel with coords (x, y) + int GetNLL(int x, int y); + + // return grayscale value [0, 255] of pixel with coords (x, y) + int GetPixelGray(int x, int y); + + // output Pixels of specific class + // return number of pixels drawn + size_t OutputClass(size_t ClassNo, size_t MaxPixelsToOutput); + + void OutputPixel(size_t PixelNo); + void OutputPixel(int x, int y); + + // return true if pixel (x, y) is inside image + bool IsInImage(int x, int y); + + CNLLHalftoning& Parent_; + const CHalftoneFilter::image_buffer_t& Image_; + CHalftoneFilter::image_buffer_t& OutputImage_; + int x1_; + int y1_; + + std::vector Pixels_; // pixels' gray values + std::vector Classes_; // pixels' classes + size_t ImageWidth_; + size_t ImageHeight_; + + typedef struct + { + int x; + int y; + } point_t; + + typedef struct + { + size_t p1; + size_t p2; + size_t p3; + size_t p4; + } square_block_t; + + static const point_t PixelOffsets_[18]; + static const square_block_t Squares_[8]; +}; + +/* + original + const CNLLBlock::point_t CNLLBlock::PixelOffsets_[18] = + { + { 0, 0}, + {-1, 0}, + { 0, -1}, + {-1, -1}, + { 0, 1}, + {-1, 1}, + { 1, 0}, + {-2, 0}, + { 1, -1}, + {-2, -1}, + { 1, 1}, + {-2, 1}, + { 0, -2}, + {-1, -2}, + { 0, 2}, + {-1, 2}, + { 2, 0}, + {-3, 0}, + }; +*/ + +/* + const CNLLBlock::point_t CNLLBlock::PixelOffsets_[18] = + { + { 0, 0}, + {-1, -2}, + { 0, 2}, + {-3, 0}, + { 2, 0}, + {-1, -1}, + { 1, 1}, + {-1, 1}, + { 1, -1}, + {-1, 2}, + { 0, -2}, + {-2, -1}, + {-2, 1}, + { 0, -1}, + {-2, 0}, + { 1, 0}, + {-1, 0}, + { 0, 1}, + }; + + const CNLLBlock::square_block_t CNLLBlock::Squares_[8] = + { + { 2, 11, 6, 14}, + {12, 6, 15, 17}, + { 6, 14, 17, 1}, + {14, 9, 1, 16}, + {15, 17, 13, 8}, + {17, 1, 8, 18}, + { 1, 16, 18, 7}, + { 8, 18, 10, 3} + }; +*/ + +/* good + const CNLLBlock::point_t CNLLBlock::PixelOffsets_[18] = + { + { 0, 0}, + {-1, -1}, + { 1, 1}, + {-1, 1}, + { 1, -1}, + {-2, 0}, + { 2, 0}, + { 0, -2}, + { 0, 2}, + {-1, 0}, + {-2, -1}, + { 0, 1}, + {-2, 1}, + { 0, -1}, + {-3, 0}, + {-1, -2}, + {-1, 2}, + { 1, 0}, + }; + + const CNLLBlock::square_block_t CNLLBlock::Squares_[8] = + { + {16, 8, 2, 14}, + {11, 2, 6, 10}, + { 2, 14, 10, 1}, + {14, 5, 1, 18}, + { 6, 10, 13, 4}, + {10, 1, 4, 12}, + { 1, 18, 12, 3}, + { 4, 12, 17, 9} + }; +*/ + +const CNLLBlock::point_t CNLLBlock::PixelOffsets_[18] = +{ + { 0, 0}, + {-1, 1}, + {-1, -1}, + { 1, -1}, + { 1, 1}, + {-2, 0}, + { 2, 0}, + { 0, -2}, + { 0, 2}, + {-1, 0}, + {-2, -1}, + {-2, 1}, + { 0, -1}, + { 0, 1}, + {-3, 0}, + {-1, -2}, + {-1, 2}, + { 1, 0}, +}; + +const CNLLBlock::square_block_t CNLLBlock::Squares_[8] = +{ + {17, 8, 3, 13}, + {11, 3, 6, 10}, + { 3, 13, 10, 1}, + {13, 4, 1, 18}, + { 6, 10, 12, 2}, + {10, 1, 2, 14}, + { 1, 18, 14, 5}, + { 2, 14, 16, 9} +}; + + +/* + const CNLLBlock::point_t CNLLBlock::PixelOffsets_[18] = + { + { 0, 0}, + {-1, -2}, + { 0, 2}, + {-3, 0}, + { 2, 0}, + {-2, 1}, + { 1, -1}, + {-1, 0}, + { 1, 1}, + {-2, -1}, + {-1, 1}, + { 0, -2}, + { 1, 0}, + {-1, 2}, + {-1, -1}, + { 0, 1}, + {-2, 0}, + { 0, -1}, + }; +*/ + +CNLLHalftoning::CNLLHalftoning(int Threshold, image_t InputImageType, image_t OutputImageType): + CHalftoneFilter(InputImageType, OutputImageType), Threshold_(Threshold) +{ + if (GetOutputImageType() != itBW) + throw EHalftoneError(EHalftoneError::heUnsupportedImageType); +} + +CNLLHalftoning::~CNLLHalftoning() +{ +} + +bool +CNLLHalftoning::IsProcessLineSupported() +{ + return false; +} + +void +CNLLHalftoning::ProcessLine( + const buffer_t& InputLine, buffer_t& OutputLine) +{ +} + +void +CNLLHalftoning::ProcessImage( + const void* ImageData, size_t ImageWidth, size_t ImageHeight, size_t LineDelta, std::vector& OutputImage) +{ + // TODO: non-implemented yet +} + + + +void +CNLLHalftoning::ProcessImage(const std::vector& InputImage, std::vector& OutputImage) +{ + OutputImage.clear(); + if (InputImage.size() == 0) return; + + ImageWidth_ = CalcImageWidth(InputImage[0]); + ImageHeight_ = InputImage.size(); + + // create an empty output image + buffer_t EmptyLine; + EmptyLine.resize(ImageWidth_ / 8 + 1, 0); + OutputImage.resize(ImageHeight_, EmptyLine); + + // split the image to 18-pixels block + size_t RowCount = (InputImage.size() + 1) / 3 + 1; + for (size_t r = 0; r < RowCount; ++r) + { + // get coordinates of pixel #1 + size_t x1 = (r % 2) ? 3 : 0; + size_t y1 = 3 * r; + + // for all blocks in the row + // both leftest and rightest pixels of the block is + while ((x1 - 3 < ImageWidth_) || (x1 + 2 < ImageWidth_)) + { + CNLLBlock Block(*this, InputImage, x1, y1, OutputImage); + + Block.FillBlock(); + Block.OutputBlock(); + + + // advance to next block + x1 += 6; + } // for blocks in the row + } // for rows +} + +/* + void + CNLLHalftoning::ProcessImage(const std::vector& InputImage, std::vector& OutputImage) + { + OutputImage.clear(); + if (InputImage.size() == 0) return; + + ImageWidth_ = CalcImageWidth(InputImage[0]); + ImageHeight_ = InputImage.size(); + + // create an empty output image + buffer_t EmptyLine; + EmptyLine.resize(ImageWidth_ / 8 + 1, 0); + OutputImage.resize(ImageHeight_, EmptyLine); + + // split the image to 18-pixels block + size_t x1 = 0; + size_t y1 = 0; + while (ProcessDiagonal(InputImage, OutputImage, x1, y1)) + { + //x1 += 5; + //y1 += 1; + x1 += 2; + y1 += 4; + } + } +*/ + +bool +CNLLHalftoning::ProcessDiagonal( + const std::vector& InputImage, std::vector& OutputImage, size_t& x1, size_t& y1) +{ + //fprintf(stderr, "ProcessDiagonal(%i, %i)\n", x1, y1); + + bool Result = false; + bool HasDownBlocks = false; + bool HasUpBlocks = false; + size_t UpperDownX = 0; + size_t UpperDownY = 0; + + // go down + size_t x = x1 - 3; + size_t y = y1 + 3; + while (true) + { + CNLLBlock Block(*this, InputImage, x, y, OutputImage); + + if (Block.IsInImage()) + { + //fprintf(stderr, "down Block (%i, %i)\n", x, y); + Block.FillBlock(); + Block.OutputBlock(); + Result = true; + + if (!HasDownBlocks) + { + UpperDownX = x; + UpperDownY = y; + HasDownBlocks = true; + } + + x -= 3; + y += 3; + } + else + break; + } // go down + + // go up + x = x1; + y = y1; + while (true) + { + CNLLBlock Block(*this, InputImage, x, y, OutputImage); + + if (Block.IsInImage()) + { + //fprintf(stderr, "up Block (%i, %i)\n", x, y); + + Block.FillBlock(); + Block.OutputBlock(); + Result = true; + HasUpBlocks = true; + + x1 = x; + y1 = y; + x += 3; + y -= 3; + } + else + break; + } // go up + + if (Result && !HasUpBlocks) + { + x1 = UpperDownX; + y1 = UpperDownY; + } + //fprintf(stderr, "ProcessDiagonal returns (%i, %i)\n", x1, y1); + + return Result; +} + +int +CNLLHalftoning::GetThreshold() +{ + return Threshold_; +} + +//////////////////////////////////////////////////////////////////////// +// Block class methods +//////////////////////////////////////////////////////////////////////// + +CNLLBlock::CNLLBlock( + CNLLHalftoning& Parent, const CHalftoneFilter::image_buffer_t& Image, int x1, int y1, CHalftoneFilter::image_buffer_t& OutputImage): + Parent_(Parent), Image_(Image), OutputImage_(OutputImage), x1_(x1), y1_(y1), Pixels_(18, 0), Classes_(18, 0) +{ + ImageWidth_ = Parent_.CalcImageWidth(Image_[0]); + ImageHeight_ = Image_.size(); +} + +void +CNLLBlock::FillBlock() +{ + for (size_t i = 0; i < Pixels_.size(); ++i) + FillPixel(i + 1, x1_ + PixelOffsets_[i].x, y1_ + PixelOffsets_[i].y); + + ReduceClasses(); + +} + +void +CNLLBlock::ReduceClasses() +{ + //ReduceClasses(1, 2); + //ReduceClasses(5, 4); +} + +void +CNLLBlock::ReduceClasses(size_t ClassFrom, size_t ClassTo) +{ + for (size_t i = 0; i < 8; ++i) + { + if ((Classes_[Squares_[i].p1 - 1] == ClassFrom) + && (Classes_[Squares_[i].p2 - 1] == ClassFrom) + && (Classes_[Squares_[i].p3 - 1] == ClassFrom) + && (Classes_[Squares_[i].p4 - 1] == ClassFrom)) + { + Classes_[Squares_[i].p1 - 1] = ClassTo; + Classes_[Squares_[i].p3 - 1] = ClassTo; + } + } +} + +void +CNLLBlock::FillPixel(size_t PixelNo, int x, int y) +{ + // fill pixels + Pixels_[PixelNo - 1] = GetPixelGray(x, y); + + // fill classes + int NLL = GetNLL(x, y); + int Threshold = Parent_.GetThreshold(); + + // we added to new classes to those described in the papers + // 0 - (same as 1) - it is set for black pixels, those should remeined black + // 6 - (same as 5) - it is set for white pixels, those should remeined white + + if (Pixels_[PixelNo - 1] == 0) + Classes_[PixelNo - 1] = 0; + else if (Pixels_[PixelNo - 1] == 255) + Classes_[PixelNo - 1] = 6; + else // as in papers + if (NLL < -Threshold) + Classes_[PixelNo - 1] = 1; + else if (NLL > Threshold) + Classes_[PixelNo - 1] = 5; + else + Classes_[PixelNo - 1] = 3; + +} + +int +CNLLBlock::GetPixelGray(int x, int y) +{ + if (IsInImage(x, y)) + { + byte R, G, B; + Parent_.ExtractRGB(Image_[y], x, R, G, B); + return Parent_.RGBToGrayScale(R, G, B); + } + else + return 255; // white +} + +int +CNLLBlock::GetNLL(int x, int y) +{ + int A = + GetPixelGray(x, y) + - ( GetPixelGray(x - 1, y - 1) + + GetPixelGray(x + 1, y - 1) + + GetPixelGray(x - 1, y + 1) + + GetPixelGray(x + 1, y + 1)) / 4; + + int B = + GetPixelGray(x, y) + - ( GetPixelGray(x - 0, y - 1) + + GetPixelGray(x + 0, y + 1) + + GetPixelGray(x - 1, y + 0) + + GetPixelGray(x + 1, y + 0)) / 4; + + if ((A > 0) && (B > 0)) + return std::min(A, B); + + if ((A < 0) && (B < 0)) + return -std::min(abs(A), abs(B)); + + return 0; +} + + +size_t +CNLLBlock::GetBlockIntenseValue() +{ + size_t Intense = 128; + for (size_t i = 0; i < Pixels_.size(); ++i) + Intense += Pixels_[i]; + + size_t result = 18 - std::min(Intense / 255, size_t(18)); + //fprintf(stderr, "GetBlockIntenseValue() = %d\n", result); + return result; +} + +void +CNLLBlock::OutputBlock() +{ + size_t RemainedPixels = GetBlockIntenseValue(); + size_t PixelCount = 0; + + // output all pixels for class 0 + PixelCount = OutputClass(0, 18); + + if (PixelCount < RemainedPixels) + { + RemainedPixels -= PixelCount; + RemainedPixels -= OutputClass(1, RemainedPixels); + RemainedPixels -= OutputClass(2, RemainedPixels); + RemainedPixels -= OutputClass(3, RemainedPixels); + RemainedPixels -= OutputClass(4, RemainedPixels); + } +} + +size_t +CNLLBlock::OutputClass(size_t ClassNo, size_t MaxPixelsToOutput) +{ + //fprintf(stderr, "OutputClass(%i, %i)\n", ClassNo, MaxPixelsToOutput); + + if (MaxPixelsToOutput == 0) + return 0; + + std::vector Pixels; + + // collect all pixels of a class + for (size_t i = 0; i < Classes_.size(); ++i) + if (Classes_[i] == ClassNo) + Pixels.push_back(i + 1); + + // sort pixels, so output it in order + //std::sort(Pixels.begin(), Pixels.end()); + //std::random_shuffle(Pixels.begin(), Pixels.end()); + + // output pixels + size_t Result = 0; + for (size_t i = 0; i < Pixels.size(); ++i) + if (Result < MaxPixelsToOutput) + { + OutputPixel(Pixels[i]); + ++Result; + } + else + break; + + return Result; +} + +void +CNLLBlock::OutputPixel(size_t PixelNo) +{ + OutputPixel(x1_ + PixelOffsets_[PixelNo - 1].x, y1_ + PixelOffsets_[PixelNo - 1].y); +} + +void +CNLLBlock::OutputPixel(int x, int y) +{ + if (IsInImage(x, y)) + Parent_.SetPixelBW(OutputImage_[y], x, 1); +} + +bool +CNLLBlock::IsInImage() +{ + for (size_t i = 0; i < Pixels_.size(); ++i) + if (IsInImage(x1_ + PixelOffsets_[i].x, y1_ + PixelOffsets_[i].y)) + return true; + + return false; +} + +bool +CNLLBlock::IsInImage(int x, int y) +{ + return (x >= 0) && (size_t(x) < ImageWidth_) && (y >= 0) && (size_t(y) < ImageHeight_); +} + + +} // namespace + +/* + * End of "$Id: NonLinearLaplacianHalftoning.cpp 4759 2008-06-19 19:02:27Z vbuzuev $". + */ diff --git a/halftoning/NonLinearLaplacianHalftoning.h b/halftoning/NonLinearLaplacianHalftoning.h new file mode 100644 index 0000000..113a2c1 --- /dev/null +++ b/halftoning/NonLinearLaplacianHalftoning.h @@ -0,0 +1,64 @@ +// -*- C++ -*- +// $Id: NonLinearLaplacianHalftoning.h 4759 2008-06-19 19:02:27Z vbuzuev $ + +// DYMO LabelWriter Drivers +// Copyright (C) 2008 Sanford L.P. + +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +#ifndef heebf43e9_0acf_490a_8385_1a339afa4da1 +#define heebf43e9_0acf_490a_8385_1a339afa4da1 + +#include "Halftoning.h" + +//namespace dymo +namespace DymoPrinterDriver +{ + +class CNLLHalftoning: public CHalftoneFilter +{ +public: + CNLLHalftoning(int Threshold, image_t InputImageType, image_t OutputImageType); + virtual ~CNLLHalftoning(); + + virtual bool IsProcessLineSupported(); + virtual void ProcessLine(const buffer_t& InputLine, buffer_t& OutputLine); + virtual void ProcessImage(const void* ImageData, size_t ImageWidth, size_t ImageHeight, size_t LineDelta, std::vector& OutputImage); + virtual void ProcessImage(const image_buffer_t& InputImage, image_buffer_t& OutputImage); + + int GetThreshold(); +protected: +private: + int Threshold_; // constant used to separate a block to classes using NLL + + size_t ImageWidth_; + size_t ImageHeight_; + + // split image to 18-pixels block be diagonal + // return true if diagonal contains at least one Block inside image, so next diagonal should be processes + // on output (x1, y1) is coodrs of pixel #1 of topmost block in the diagonal + bool ProcessDiagonal( + const std::vector& InputImage, std::vector& OutputImage, size_t& x1, size_t& y1); + +}; + +}; // namespace + +#endif + +/* + * End of "$Id: NonLinearLaplacianHalftoning.h 4759 2008-06-19 19:02:27Z vbuzuev $". + */ diff --git a/rastertoptch.c b/rastertoptch.c index b97da73..1cfac68 100644 --- a/rastertoptch.c +++ b/rastertoptch.c @@ -277,6 +277,8 @@ #include #include +#include "halftoning/HalftoneHelper.h" + static const char* progname; /** Length of a PostScript point in mm */ @@ -303,6 +305,7 @@ typedef enum { */ typedef enum {RIGHT, CENTER} align_t; typedef enum {TAPE, LABELS} media_t; +typedef enum {DEFAULT=-1, ERR_DIFF, NLL, HALFTONE_MAX=NLL} halftone_t; /** CUPS Raster line buffer. */ unsigned char* buffer; @@ -377,6 +380,7 @@ typedef struct { float min_margin; /**< minimum top and bottom margin */ float margin; /**< top and bottom margin */ int status_notification; /**< automatic status notification */ + int halftone; /**< Halftone selection */ unsigned int page; /**< The current page number */ bool last_page; /**< This is the last page */ } job_options_t; @@ -391,7 +395,7 @@ job_options_t parse_job_options (const char* str) { job_options_t options = { /* pixel_xfer */ RLE, - /* print_quality_high */ true, + /* print_quality_high */ CUPS_TRUE, /* auto_cut */ false, /* half_cut */ false, /* cut_mark */ false, @@ -415,6 +419,8 @@ parse_job_options (const char* str) { /* min_margin */ 0.0, /* margin */ 0.0, /* status_notification (don't set) */ -1, + /* halftone */ DEFAULT, + }; struct int_option { @@ -430,6 +436,7 @@ parse_job_options (const char* str) { { "LegacyTransferMode", &options.legacy_xfer_mode, 0, 255 }, { "TransferMode", &options.xfer_mode, 0, 255 }, { "StatusNotification", &options.status_notification, 0, 1 }, + { "HalfTone", &options.halftone, -1, 1 }, { } }; @@ -495,11 +502,11 @@ parse_job_options (const char* str) { if (strcasecmp (name, "PrintQuality") == 0) { if (value) { if (strcasecmp (value, "High") == 0) { - options.print_quality_high = true; + options.print_quality_high = CUPS_TRUE; continue; } if (strcasecmp (value, "Fast") == 0) { - options.print_quality_high = false; + options.print_quality_high = CUPS_FALSE; continue; } } @@ -634,8 +641,8 @@ page_prepare (unsigned cups_buffer_size, unsigned device_buffer_size) { #endif /* Allocate line buffer */ - buffer = malloc (cups_buffer_size); - emit_line_buffer = malloc (device_buffer_size); + buffer = (unsigned char*) malloc (cups_buffer_size); + emit_line_buffer = (unsigned char*) malloc (device_buffer_size); if (!buffer || !emit_line_buffer) { fprintf (stderr, @@ -1105,7 +1112,7 @@ ensure_rle_buf_space (job_options_t* job_options, if (new_alloced <= 1000000) p = realloc (rle_buffer, new_alloced * sizeof (char)); if (p) { - rle_buffer = p; + rle_buffer = (unsigned char*) p; rle_buffer_next = rle_buffer + nextpos; rle_alloced = new_alloced; } else { /* Gain memory by flushing buffer to printer */ @@ -1339,8 +1346,8 @@ emit_raster_lines (job_options_t* job_options, /* Calculate extra horizontal spacing pixels if the right side of */ /* cupsImagingBBox doesn't touch the cupsPageSize box */ float pt2px[] = { - header->HWResolution [0] / 72.0, - header->HWResolution [1] / 72.0 + header->HWResolution [0] / 72.0f, + header->HWResolution [1] / 72.0f }; unsigned right_spacing_px = 0; if (header->cupsImagingBBox[2] < header->cupsPageSize[0]) { @@ -1389,6 +1396,8 @@ emit_raster_lines (job_options_t* job_options, if (image_height_px >= top_empty_lines + cupsHeight) bot_empty_lines = image_height_px - top_empty_lines - cupsHeight; + unsigned halftone_bytes = buflen; + /* * QL printers have a specific top and bottom margin that must be left blank * to allow printers to skip to the next label. For continuous-length tape, @@ -1436,17 +1445,52 @@ emit_raster_lines (job_options_t* job_options, /* Generate and store actual page data */ empty_lines += top_empty_lines; - int y; + int y, result; + + // This filter needs all of the image at once. + if (job_options->halftone == NLL) + { + nll_clear_buffers(); + for (y = 0; y < cupsHeight; y++) { + if (cupsRasterReadPixels (ras, buffer, cupsBytesPerLine) < 1) + break; /* Escape if no pixels read */ + nll_add_line(buffer, cupsBytesPerLine); + } + nll_process(); + } + + for (y = 0; y < cupsHeight; y++) { /* Feedback to the user */ progress.completed = y; /* Read one line of pixels */ - if (cupsRasterReadPixels (ras, buffer, cupsBytesPerLine) < 1) + if (job_options->halftone == NLL) + { + result = nll_get_next_line(buffer, cupsBytesPerLine); + } + else + { + result = cupsRasterReadPixels (ras, buffer, cupsBytesPerLine); + } + if (result < 1) break; /* Escape if no pixels read */ if (y < top_skip || y + bot_skip >= cupsHeight) continue; + switch (job_options->halftone) { + case ERR_DIFF: + fprintf(stderr, "cbpl %d bpl %d\n", cupsBytesPerLine, bytes_per_line); + halftone_bytes = do_halftone_err_diff(buffer, cupsBytesPerLine); + break; + case NLL: + halftone_bytes = result; + break; + default: + /* NO-OP */ + halftone_bytes = buflen; + break; + } bool nonempty_line = - generate_emit_line (buffer, emit_line_buffer, buflen, bytes_per_line, + generate_emit_line (buffer, emit_line_buffer, halftone_bytes, bytes_per_line, right_padding_bytes, shift, do_mirror, xormask); if (nonempty_line) { if (empty_lines) { @@ -1490,8 +1534,8 @@ process_rasterdata (job_options_t* job_options) { header = tmp_header, job_options->page++) { float pt2px[] = { - header->HWResolution [0] / 72.0, - header->HWResolution [1] / 72.0 + header->HWResolution [0] / 72.0f, + header->HWResolution [1] / 72.0f }; fprintf (stderr, "DEBUG: %s: PageSize: %.2fx%.2f pt / %.2fx%.2f mm / %.2fx%.2f px\n", progname, @@ -1516,6 +1560,8 @@ process_rasterdata (job_options_t* job_options) { header->cupsWidth, header->cupsHeight); fprintf (stderr, "DEBUG: %s: NegativePrint: %d\n", progname, header->NegativePrint); + fprintf (stderr, "DEBUG: %s: Halftone: %d\n", + progname, job_options->halftone); page_prepare (header->cupsBytesPerLine, bytes_per_line); if (job_options->page == 1) { emit_job_cmds (job_options);