mirror of
https://github.com/JakubMelka/PDF4QT.git
synced 2025-06-05 21:59:17 +02:00
Issue #107: Conversion algorithm
This commit is contained in:
188
Pdf4QtLib/sources/pdfimageconversion.cpp
Normal file
188
Pdf4QtLib/sources/pdfimageconversion.cpp
Normal file
@ -0,0 +1,188 @@
|
||||
#include "pdfimageconversion.h"
|
||||
|
||||
namespace pdf
|
||||
{
|
||||
|
||||
PDFImageConversion::PDFImageConversion()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void PDFImageConversion::setImage(QImage image)
|
||||
{
|
||||
m_image = std::move(image);
|
||||
m_convertedImage = QImage();
|
||||
m_automaticThreshold = DEFAULT_THRESHOLD;
|
||||
}
|
||||
|
||||
void PDFImageConversion::setConversionMethod(ConversionMethod method)
|
||||
{
|
||||
m_conversionMethod = method;
|
||||
}
|
||||
|
||||
void PDFImageConversion::setThreshold(int threshold)
|
||||
{
|
||||
m_manualThreshold = threshold;
|
||||
}
|
||||
|
||||
bool PDFImageConversion::convert()
|
||||
{
|
||||
if (m_image.isNull())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
QImage bitonal(m_image.width(), m_image.height(), QImage::Format_Mono);
|
||||
bitonal.fill(0);
|
||||
|
||||
// Thresholding
|
||||
int threshold = DEFAULT_THRESHOLD;
|
||||
|
||||
switch (m_conversionMethod)
|
||||
{
|
||||
case pdf::PDFImageConversion::ConversionMethod::Automatic:
|
||||
m_automaticThreshold = calculateOtsu1DThreshold();
|
||||
threshold = m_automaticThreshold;
|
||||
break;
|
||||
|
||||
case pdf::PDFImageConversion::ConversionMethod::Manual:
|
||||
threshold = m_manualThreshold;
|
||||
break;
|
||||
|
||||
default:
|
||||
Q_ASSERT(false);
|
||||
break;
|
||||
}
|
||||
|
||||
for (int y = 0; y < m_image.height(); ++y)
|
||||
{
|
||||
for (int x = 0; x < m_image.width(); ++x)
|
||||
{
|
||||
QColor pixelColor = m_image.pixelColor(x, y);
|
||||
int pixelValue = pixelColor.lightness();
|
||||
bool bit = (pixelValue >= threshold);
|
||||
bitonal.setPixel(x, y, bit);
|
||||
}
|
||||
}
|
||||
|
||||
m_convertedImage = std::move(bitonal);
|
||||
return true;
|
||||
}
|
||||
|
||||
int PDFImageConversion::getThreshold() const
|
||||
{
|
||||
switch (m_conversionMethod)
|
||||
{
|
||||
case pdf::PDFImageConversion::ConversionMethod::Automatic:
|
||||
return m_automaticThreshold;
|
||||
|
||||
case pdf::PDFImageConversion::ConversionMethod::Manual:
|
||||
return m_manualThreshold;
|
||||
|
||||
default:
|
||||
Q_ASSERT(false);
|
||||
break;
|
||||
}
|
||||
|
||||
return DEFAULT_THRESHOLD;
|
||||
}
|
||||
|
||||
QImage PDFImageConversion::getConvertedImage() const
|
||||
{
|
||||
return m_convertedImage;
|
||||
}
|
||||
|
||||
int PDFImageConversion::calculateOtsu1DThreshold() const
|
||||
{
|
||||
if (m_image.isNull())
|
||||
{
|
||||
return 128;
|
||||
}
|
||||
|
||||
// Histogram of lightness occurences
|
||||
std::array<int, 256> histogram = { };
|
||||
|
||||
for (int x = 0; x < m_image.width(); ++x)
|
||||
{
|
||||
for (int y = 0; y < m_image.height(); ++y)
|
||||
{
|
||||
int lightness = m_image.pixelColor(x, y).lightness();
|
||||
Q_ASSERT(lightness >= 0 && lightness <= 255);
|
||||
|
||||
int clampedLightness = qBound(0, lightness, 255);
|
||||
histogram[clampedLightness] += 1;
|
||||
}
|
||||
}
|
||||
|
||||
float factor = 1.0f / float(m_image.width() * m_image.height());
|
||||
|
||||
std::array<float, 256> normalizedHistogram = { };
|
||||
std::array<float, 256> cumulativeProbabilities = { };
|
||||
std::array<float, 256> interClassVariance = { };
|
||||
|
||||
// Compute probabilities
|
||||
for (size_t i = 0; i < histogram.size(); ++i)
|
||||
{
|
||||
normalizedHistogram[i] = histogram[i] * factor;
|
||||
cumulativeProbabilities[i] = normalizedHistogram[i];
|
||||
|
||||
if (i > 0)
|
||||
{
|
||||
cumulativeProbabilities[i] += cumulativeProbabilities[i - 1];
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the inter-class variance for each threshold. Variables
|
||||
// with the subscript 0 denote the background, while those with
|
||||
// subscript 1 denote the foreground.
|
||||
for (size_t i = 0; i < histogram.size(); ++i)
|
||||
{
|
||||
const float w0 = cumulativeProbabilities[i] - normalizedHistogram[i];
|
||||
const float w1 = 1.0f - w0;
|
||||
|
||||
float u0 = 0.0f;
|
||||
float u1 = 0.0f;
|
||||
|
||||
// Calculate mean intensity value of the background.
|
||||
if (!qFuzzyIsNull(w0))
|
||||
{
|
||||
for (size_t j = 0; j < i; ++j)
|
||||
{
|
||||
u0 += j * normalizedHistogram[j];
|
||||
}
|
||||
|
||||
u0 /= w0;
|
||||
}
|
||||
|
||||
// Calculate mean intensity value of the foreground.
|
||||
if (!qFuzzyIsNull(w1))
|
||||
{
|
||||
for (size_t j = i; j < histogram.size(); ++j)
|
||||
{
|
||||
u1 += j * normalizedHistogram[j];
|
||||
}
|
||||
|
||||
u1 /= w1;
|
||||
}
|
||||
|
||||
const float variance = w0 * w1 * std::powf(u0 - u1, 2);
|
||||
interClassVariance[i] = variance;
|
||||
}
|
||||
|
||||
// Find maximal value of the variance
|
||||
size_t maxVarianceIndex = 0;
|
||||
float maxVarianceValue = 0.0f;
|
||||
|
||||
for (size_t i = 0; i < interClassVariance.size(); ++i)
|
||||
{
|
||||
if (interClassVariance[i] > maxVarianceValue)
|
||||
{
|
||||
maxVarianceValue = interClassVariance[i];
|
||||
maxVarianceIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
return int(maxVarianceIndex);
|
||||
}
|
||||
|
||||
} // namespace pdf
|
97
Pdf4QtLib/sources/pdfimageconversion.h
Normal file
97
Pdf4QtLib/sources/pdfimageconversion.h
Normal file
@ -0,0 +1,97 @@
|
||||
// Copyright (C) 2023 Jakub Melka
|
||||
//
|
||||
// This file is part of PDF4QT.
|
||||
//
|
||||
// PDF4QT is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// with the written consent of the copyright owner, any later version.
|
||||
//
|
||||
// PDF4QT 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 Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with PDF4QT. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#ifndef PDFIMAGECONVERSION_H
|
||||
#define PDFIMAGECONVERSION_H
|
||||
|
||||
#include "pdfglobal.h"
|
||||
|
||||
#include <QImage>
|
||||
|
||||
namespace pdf
|
||||
{
|
||||
|
||||
/// This class facilitates various image conversions,
|
||||
/// including transforming colored images into monochromatic (or bitonal) formats.
|
||||
class PDF4QTLIBSHARED_EXPORT PDFImageConversion
|
||||
{
|
||||
public:
|
||||
PDFImageConversion();
|
||||
|
||||
enum class ConversionMethod
|
||||
{
|
||||
Automatic, ///< The threshold is determined automatically using an algorithm.
|
||||
Manual ///< The threshold is manually provided by the user.
|
||||
};
|
||||
|
||||
/// Sets the image to be converted using the specified conversion method.
|
||||
/// This operation resets any previously converted image and the automatic threshold,
|
||||
/// thereby erasing all prior image data.
|
||||
/// \param image The image to be set for conversion.
|
||||
void setImage(QImage image);
|
||||
|
||||
/// Sets the method for image conversion. Multiple methods are available
|
||||
/// for selection. If the manual method is chosen, an appropriate threshold
|
||||
/// must also be set by the user.
|
||||
/// \param method The conversion method to be used.
|
||||
void setConversionMethod(ConversionMethod method);
|
||||
|
||||
/// Sets the manual threshold value. When a non-manual (e.g., automatic) conversion
|
||||
/// method is in use, this function will retain the manual threshold settings,
|
||||
/// but the conversion will utilize an automatically calculated threshold for the image.
|
||||
/// The manually set threshold is preserved and not overwritten. Therefore, if the
|
||||
/// manual conversion method is later selected, the previously established manual
|
||||
/// threshold will be applied.
|
||||
/// \param threshold The manual threshold value to be set.
|
||||
void setThreshold(int threshold);
|
||||
|
||||
/// This method converts the image into a bitonal (monochromatic) format. If
|
||||
/// the automatic threshold calculation is enabled, it executes Otsu's 1D algorithm
|
||||
/// to determine the threshold. When the manual conversion method is selected,
|
||||
/// the automatic threshold calculation is bypassed, and the predefined manual threshold
|
||||
/// value is utilized instead. This method returns true if the conversion is
|
||||
/// successful, and false otherwise.
|
||||
bool convert();
|
||||
|
||||
/// Returns the threshold used in image conversion. If the automatic conversion method is
|
||||
/// selected, this function should be called only after executing the convert() method;
|
||||
/// otherwise, it may return invalid data. The automatic threshold calculation is
|
||||
/// performed within the convert() method.
|
||||
/// \returns The threshold value used in image conversion.
|
||||
int getThreshold() const;
|
||||
|
||||
/// Returns the converted image. This method should only be called after
|
||||
/// the convert() method has been executed, and additionally, only if the
|
||||
/// convert() method returns true. If these conditions are not met, the result
|
||||
/// is undefined.
|
||||
QImage getConvertedImage() const;
|
||||
|
||||
private:
|
||||
int calculateOtsu1DThreshold() const;
|
||||
|
||||
static constexpr int DEFAULT_THRESHOLD = 128;
|
||||
|
||||
QImage m_image;
|
||||
QImage m_convertedImage;
|
||||
ConversionMethod m_conversionMethod = ConversionMethod::Automatic;
|
||||
int m_automaticThreshold = DEFAULT_THRESHOLD;
|
||||
int m_manualThreshold = DEFAULT_THRESHOLD;
|
||||
};
|
||||
|
||||
} // namespace pdf
|
||||
|
||||
#endif // PDFIMAGECONVERSION_H
|
Reference in New Issue
Block a user