Free form gourad triangle shading

This commit is contained in:
Jakub Melka 2019-09-15 16:50:34 +02:00
parent 40290fd2ec
commit dc6bc3e96c
7 changed files with 433 additions and 23 deletions

View File

@ -110,8 +110,7 @@ QImage PDFAbstractColorSpace::getImage(const PDFImageData& imageData) const
throw PDFParserException(PDFTranslationContext::tr("Invalid size of the decoded array. Expected %1, actual %2.").arg(componentCount * 2).arg(decode.size()));
}
QDataStream stream(const_cast<QByteArray*>(&imageData.getData()), QIODevice::ReadOnly);
PDFBitReader reader(&stream, imageData.getBitsPerComponent());
PDFBitReader reader(&imageData.getData(), imageData.getBitsPerComponent());
PDFColor color;
color.resize(componentCount);
@ -176,8 +175,7 @@ QImage PDFAbstractColorSpace::getImage(const PDFImageData& imageData) const
throw PDFParserException(PDFTranslationContext::tr("Invalid size of the decoded array. Expected %1, actual %2.").arg(componentCount * 2).arg(decode.size()));
}
QDataStream stream(const_cast<QByteArray*>(&imageData.getData()), QIODevice::ReadOnly);
PDFBitReader reader(&stream, imageData.getBitsPerComponent());
PDFBitReader reader(&imageData.getData(), imageData.getBitsPerComponent());
PDFColor color;
color.resize(componentCount);
@ -822,8 +820,7 @@ QImage PDFIndexedColorSpace::getImage(const PDFImageData& imageData) const
image.fill(QColor(Qt::white));
unsigned int componentCount = imageData.getComponents();
QDataStream stream(const_cast<QByteArray*>(&imageData.getData()), QIODevice::ReadOnly);
PDFBitReader reader(&stream, imageData.getBitsPerComponent());
PDFBitReader reader(&imageData.getData(), imageData.getBitsPerComponent());
if (componentCount != getColorComponentCount())
{

View File

@ -547,8 +547,7 @@ QImage PDFImage::getImage() const
QImage image(m_imageData.getWidth(), m_imageData.getHeight(), QImage::Format_Alpha8);
const bool flip01 = !m_imageData.getDecode().empty() && qFuzzyCompare(m_imageData.getDecode().front(), 1.0);
QDataStream stream(const_cast<QByteArray*>(&m_imageData.getData()), QIODevice::ReadOnly);
PDFBitReader reader(&stream, m_imageData.getBitsPerComponent());
PDFBitReader reader(&m_imageData.getData(), m_imageData.getBitsPerComponent());
for (unsigned int i = 0, rowCount = m_imageData.getHeight(); i < rowCount; ++i)
{

View File

@ -613,7 +613,7 @@ void PDFPageContentProcessor::processPathPainting(const QPainterPath& path, bool
// We must create a mesh and then draw pattern
PDFMeshQualitySettings settings;
settings.deviceSpaceMeshingArea = getPageBoundingRectDeviceSpace();
settings.userSpaceToDeviceSpaceMatrix = getPatternBaseMatrix();
settings.userSpaceToDeviceSpaceMatrix = getCurrentWorldMatrix();
settings.initDefaultResolution();
PDFMesh mesh = shadingPatern->createMesh(settings);
@ -665,7 +665,7 @@ void PDFPageContentProcessor::processPathPainting(const QPainterPath& path, bool
// We must create a mesh and then draw pattern
PDFMeshQualitySettings settings;
settings.deviceSpaceMeshingArea = getPageBoundingRectDeviceSpace();
settings.userSpaceToDeviceSpaceMatrix = getPatternBaseMatrix();
settings.userSpaceToDeviceSpaceMatrix = getCurrentWorldMatrix();
settings.initDefaultResolution();
PDFMesh mesh = shadingPatern->createMesh(settings);

View File

@ -289,6 +289,82 @@ PDFPatternPtr PDFPattern::createShadingPattern(const PDFDictionary* colorSpaceDi
return result;
}
case ShadingType::FreeFormGouradTriangle:
case ShadingType::LatticeFormGouradTriangle:
{
PDFFreeFormGouradTriangleShading* freeFormGouradTriangleShading = nullptr;
PDFLatticeFormGouradTriangleShading* latticeFormGouradTriangleShading = nullptr;
PDFGouradTriangleShading* gouradTriangleShading = nullptr;
if (shadingType == ShadingType::FreeFormGouradTriangle)
{
freeFormGouradTriangleShading = new PDFFreeFormGouradTriangleShading();
gouradTriangleShading = freeFormGouradTriangleShading;
}
else
{
Q_ASSERT(shadingType == ShadingType::LatticeFormGouradTriangle);
latticeFormGouradTriangleShading = new PDFLatticeFormGouradTriangleShading();
gouradTriangleShading = latticeFormGouradTriangleShading;
}
PDFPatternPtr result(gouradTriangleShading);
PDFInteger bitsPerCoordinate = loader.readIntegerFromDictionary(shadingDictionary, "BitsPerCoordinate", -1);
if (!contains(bitsPerCoordinate, std::initializer_list<PDFInteger>{ 1, 2, 4, 8, 12, 16, 24, 32 }))
{
throw PDFParserException(PDFTranslationContext::tr("Invalid bits per coordinate (%1) for gourad triangle meshing.").arg(bitsPerCoordinate));
}
PDFInteger bitsPerComponent = loader.readIntegerFromDictionary(shadingDictionary, "BitsPerComponent", -1);
if (!contains(bitsPerComponent, std::initializer_list<PDFInteger>{ 1, 2, 4, 8, 12, 16 }))
{
throw PDFParserException(PDFTranslationContext::tr("Invalid bits per component (%1) for gourad triangle meshing.").arg(bitsPerComponent));
}
std::vector<PDFReal> decode = loader.readNumberArrayFromDictionary(shadingDictionary, "Decode");
if (!functions.empty())
{
if (decode.size() != 6)
{
throw PDFParserException(PDFTranslationContext::tr("Invalid domain of gourad triangle meshing. Expected size is 6, actual size is %1.").arg(decode.size()));
}
}
else
{
const size_t expectedSize = colorSpace->getColorComponentCount() * 2 + 4;
if (decode.size() != expectedSize)
{
throw PDFParserException(PDFTranslationContext::tr("Invalid domain of gourad triangle meshing. Expected size is %1, actual size is %2.").arg(expectedSize).arg(decode.size()));
}
}
gouradTriangleShading->m_antiAlias = antialias;
gouradTriangleShading->m_backgroundColor = backgroundColor;
gouradTriangleShading->m_colorSpace = colorSpace;
gouradTriangleShading->m_patternGraphicState = patternGraphicState;
gouradTriangleShading->m_bitsPerCoordinate = static_cast<uint8_t>(bitsPerCoordinate);
gouradTriangleShading->m_bitsPerComponent = static_cast<uint8_t>(bitsPerComponent);
gouradTriangleShading->m_xmin = decode[0];
gouradTriangleShading->m_xmax = decode[1];
gouradTriangleShading->m_ymin = decode[2];
gouradTriangleShading->m_ymax = decode[3];
gouradTriangleShading->m_limits = std::vector<PDFReal>(std::next(decode.cbegin(), 4), decode.cend());
gouradTriangleShading->m_colorComponentCount = !functions.empty() ? 1 : colorSpace->getColorComponentCount();
gouradTriangleShading->m_functions = qMove(functions);
gouradTriangleShading->m_data = document->getDecodedStream(stream);
if (shadingType == ShadingType::FreeFormGouradTriangle)
{
PDFInteger bitsPerFlag = loader.readIntegerFromDictionary(shadingDictionary, "BitsPerFlag", -1);
if (!contains(bitsPerFlag, std::initializer_list<PDFInteger>{ 2, 4, 8 }))
{
throw PDFParserException(PDFTranslationContext::tr("Invalid bits per flag (%1) for gourad triangle meshing.").arg(bitsPerFlag));
}
freeFormGouradTriangleShading->m_bitsPerFlag = bitsPerFlag;
}
return result;
}
default:
{
throw PDFParserException(PDFTranslationContext::tr("Invalid shading pattern type (%1).").arg(static_cast<PDFInteger>(shadingType)));
@ -1188,6 +1264,267 @@ PDFMesh PDFRadialShading::createMesh(const PDFMeshQualitySettings& settings) con
return mesh;
}
ShadingType PDFFreeFormGouradTriangleShading::getShadingType() const
{
return ShadingType::FreeFormGouradTriangle;
}
PDFMesh PDFFreeFormGouradTriangleShading::createMesh(const PDFMeshQualitySettings& settings) const
{
PDFMesh mesh;
size_t bitsPerVertex = m_bitsPerFlag + 2 * m_bitsPerCoordinate + m_colorComponentCount * m_bitsPerComponent;
size_t remainder = (8 - (bitsPerVertex % 8)) % 8;
bitsPerVertex += remainder;
size_t bytesPerVertex = bitsPerVertex / 8;
size_t vertexCount = m_data.size() / bytesPerVertex;
if (vertexCount < 3)
{
// No mesh produced
return mesh;
}
// We have 3 vertices for start triangle, then for each new vertex, we get
// a new triangle, or, based on flags, no triangle (if new triangle is processed)
size_t triangleCount = vertexCount - 2;
mesh.reserve(0, triangleCount);
struct VertexData
{
uint32_t index = 0;
uint8_t flags = 0;
QPointF position;
PDFColor color;
};
const PDFReal vertexScaleRatio = 1.0 / double((static_cast<uint64_t>(1) << m_bitsPerCoordinate) - 1);
const PDFReal xScaleRatio = (m_xmax - m_xmin) * vertexScaleRatio;
const PDFReal yScaleRatio = (m_ymax - m_ymin) * vertexScaleRatio;
const PDFReal colorScaleRatio = 1.0 / double((static_cast<uint64_t>(1) << m_bitsPerComponent) - 1);
std::vector<VertexData> vertices;
vertices.resize(vertexCount);
std::vector<size_t> indices(vertexCount, 0);
std::iota(indices.begin(), indices.end(), static_cast<size_t>(0));
std::vector<QPointF> meshVertices;
meshVertices.resize(vertexCount);
auto readVertex = [this, &vertices, &settings, &meshVertices, bytesPerVertex, xScaleRatio, yScaleRatio, colorScaleRatio](size_t index)
{
PDFBitReader reader(&m_data, 8);
reader.seek(index * bytesPerVertex);
VertexData data;
data.index = static_cast<uint32_t>(index);
data.flags = reader.read(m_bitsPerFlag);
const PDFReal x = m_xmin + (reader.read(m_bitsPerCoordinate)) * xScaleRatio;
const PDFReal y = m_ymin + (reader.read(m_bitsPerCoordinate)) * yScaleRatio;
data.position = settings.userSpaceToDeviceSpaceMatrix.map(QPointF(x, y));
data.color.resize(m_colorComponentCount);
meshVertices[index] = data.position;
for (size_t i = 0; i < m_colorComponentCount; ++i)
{
const double cMin = m_limits[2 * i + 0];
const double cMax = m_limits[2 * i + 1];
data.color[i] = cMin + (reader.read(m_bitsPerComponent)) * (cMax - cMin) * colorScaleRatio;
}
if (!m_functions.empty())
{
Q_ASSERT(m_colorComponentCount == 1);
const PDFReal t = data.color[0];
std::vector<PDFReal> colorBuffer(m_colorSpace->getColorComponentCount(), 0.0);
if (m_functions.size() == 1)
{
PDFFunction::FunctionResult result = m_functions.front()->apply(&t, &t + 1, colorBuffer.data(), colorBuffer.data() + colorBuffer.size());
if (!result)
{
throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Error occured during mesh creation of shading: %1").arg(result.errorMessage));
}
}
else
{
for (size_t i = 0, count = colorBuffer.size(); i < count; ++i)
{
PDFFunction::FunctionResult result = m_functions[i]->apply(&t, &t + 1, colorBuffer.data() + i, colorBuffer.data() + i + 1);
if (!result)
{
throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Error occured during mesh creation of shading: %1").arg(result.errorMessage));
}
}
}
data.color = PDFAbstractColorSpace::convertToColor(colorBuffer);
}
vertices[index] = qMove(data);
};
std::for_each(std::execution::parallel_policy(), indices.begin(), indices.end(), readVertex);
mesh.setVertices(qMove(meshVertices));
vertices.front().flags = 0;
const VertexData* va = nullptr;
const VertexData* vb = nullptr;
const VertexData* vc = nullptr;
const VertexData* vd = nullptr;
auto addTriangle = [this, &settings, &mesh, &vertices](const VertexData* va, const VertexData* vb, const VertexData* vc)
{
const uint32_t via = va->index;
const uint32_t vib = vb->index;
const uint32_t vic = vc->index;
addSubdividedTriangles(settings, mesh, via, vib, vic, va->color, vb->color, vc->color);
};
for (size_t i = 0; i < vertexCount;)
{
vd = &vertices[i];
switch (vd->flags)
{
case 0:
{
if (i + 2 >= vertexCount)
{
throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Invalid free form gourad triangle data stream - not enough vertices."));
}
va = vd;
vb = &vertices[i + 1];
vc = &vertices[i + 2];
i += 3;
addTriangle(va, vb, vc);
break;
}
case 1:
{
// Triangle vb, vc, vd
va = vb;
vb = vc;
vc = vd;
++i;
addTriangle(va, vb, vc);
break;
}
case 2:
{
// Triangle va, vc, vd
vb = vc;
vc = vd;
++i;
addTriangle(va, vb, vc);
break;
}
default:
throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Invalid free form gourad triangle data stream - invalid vertex flag %1.").arg(vd->flags));
break;
}
}
return mesh;
}
ShadingType PDFLatticeFormGouradTriangleShading::getShadingType() const
{
return ShadingType::LatticeFormGouradTriangle;
}
PDFMesh PDFLatticeFormGouradTriangleShading::createMesh(const PDFMeshQualitySettings& settings) const
{
PDFMesh mesh;
return mesh;
}
void PDFGouradTriangleShading::addSubdividedTriangles(const PDFMeshQualitySettings& settings,
PDFMesh& mesh, uint32_t v1, uint32_t v2, uint32_t v3,
PDFColor c1, PDFColor c2, PDFColor c3) const
{
// First, verify, if we can subdivide the triangle
QLineF v12(mesh.getVertex(v1), mesh.getVertex(v2));
QLineF v13(mesh.getVertex(v1), mesh.getVertex(v3));
QLineF v23(mesh.getVertex(v2), mesh.getVertex(v3));
const qreal length12 = v12.length();
const qreal length13 = v13.length();
const qreal length23 = v23.length();
const qreal maxLength = qMax(length12, qMax(length13, length23));
const bool isColorEqual = PDFAbstractColorSpace::isColorEqual(c1, c2, settings.tolerance) &&
PDFAbstractColorSpace::isColorEqual(c1, c3, settings.tolerance) &&
PDFAbstractColorSpace::isColorEqual(c2, c3, settings.tolerance);
const bool canSubdivide = maxLength >= settings.minimalMeshResolution * 2.0; // If we subdivide, we will have length at least settings.minimalMeshResolution
if (!isColorEqual && canSubdivide)
{
if (length23 == maxLength)
{
// We split line (v2, v3), create two triangles, (v1, v2, vx) and (v1, v3, vx), where
// vx is centerpoint of line (v2, v3). We also interpolate colors.
QPointF x = v23.center();
PDFColor cx = PDFAbstractColorSpace::mixColors(c2, c3, 0.5);
const uint32_t vx = mesh.addVertex(x);
addSubdividedTriangles(settings, mesh, v1, v2, vx, c1, c2, cx);
addSubdividedTriangles(settings, mesh, v1, v3, vx, c1, c3, cx);
}
else if (length13 == maxLength)
{
// We split line (v1, v3), create two triangles, (v1, v2, vx) and (v2, v3, vx), where
// vx is centerpoint of line (v1, v3). We also interpolate colors.
QPointF x = v13.center();
PDFColor cx = PDFAbstractColorSpace::mixColors(c1, c3, 0.5);
const uint32_t vx = mesh.addVertex(x);
addSubdividedTriangles(settings, mesh, v1, v2, vx, c1, c2, cx);
addSubdividedTriangles(settings, mesh, v2, v3, vx, c2, c3, cx);
}
else
{
Q_ASSERT(length12 == maxLength);
// We split line (v1, v2), create two triangles, (v1, v3, vx) and (v2, v3, vx), where
// vx is centerpoint of line (v1, v2). We also interpolate colors.
QPointF x = v12.center();
PDFColor cx = PDFAbstractColorSpace::mixColors(c1, c2, 0.5);
const uint32_t vx = mesh.addVertex(x);
addSubdividedTriangles(settings, mesh, v1, v3, vx, c1, c3, cx);
addSubdividedTriangles(settings, mesh, v2, v3, vx, c2, c3, cx);
}
}
else
{
const size_t colorComponents = c1.size();
// Calculate color - interpolate 3 vertex colors
PDFColor color;
color.resize(colorComponents);
constexpr const PDFReal coefficient = 1.0 / 3.0;
for (size_t i = 0; i < colorComponents; ++i)
{
color[i] = (c1[i] + c2[i] + c3[i]) * coefficient;
}
Q_ASSERT(colorComponents == m_colorSpace->getColorComponentCount());
QColor transformedColor = m_colorSpace->getColor(color);
PDFMesh::Triangle triangle;
triangle.v1 = v1;
triangle.v2 = v2;
triangle.v3 = v3;
triangle.color = transformedColor.rgb();
mesh.addTriangle(triangle);
}
}
// TODO: Apply graphic state of the pattern
// TODO: Implement settings of meshing in the settings dialog

View File

@ -312,6 +312,59 @@ private:
PDFReal m_r1 = 0.0;
};
class PDFGouradTriangleShading : public PDFShadingPattern
{
public:
explicit PDFGouradTriangleShading() = default;
protected:
friend class PDFPattern;
void addSubdividedTriangles(const PDFMeshQualitySettings& settings, PDFMesh& mesh, uint32_t v1, uint32_t v2, uint32_t v3, PDFColor c1, PDFColor c2, PDFColor c3) const;
uint8_t m_bitsPerCoordinate = 0;
uint8_t m_bitsPerComponent = 0;
PDFReal m_xmin = 0.0;
PDFReal m_xmax = 0.0;
PDFReal m_ymin = 0.0;
PDFReal m_ymax = 0.0;
std::vector<PDFReal> m_limits;
size_t m_colorComponentCount;
/// Color functions. This array can be empty. If it is empty,
/// then colors should be determined directly from color space.
std::vector<PDFFunctionPtr> m_functions;
/// Data of the shading, containing triangles and colors
QByteArray m_data;
};
class PDFFreeFormGouradTriangleShading : public PDFGouradTriangleShading
{
public:
explicit PDFFreeFormGouradTriangleShading() = default;
virtual ShadingType getShadingType() const override;
virtual PDFMesh createMesh(const PDFMeshQualitySettings& settings) const override;
private:
friend class PDFPattern;
uint8_t m_bitsPerFlag = 0;
};
class PDFLatticeFormGouradTriangleShading : public PDFGouradTriangleShading
{
public:
explicit PDFLatticeFormGouradTriangleShading() = default;
virtual ShadingType getShadingType() const override;
virtual PDFMesh createMesh(const PDFMeshQualitySettings& settings) const override;
private:
friend class PDFPattern;
};
} // namespace pdf
#endif // PDFPATTERN_H

View File

@ -21,8 +21,9 @@
namespace pdf
{
PDFBitReader::PDFBitReader(QDataStream* stream, Value bitsPerComponent) :
PDFBitReader::PDFBitReader(const QByteArray* stream, Value bitsPerComponent) :
m_stream(stream),
m_position(0),
m_bitsPerComponent(bitsPerComponent),
m_maximalValue((static_cast<Value>(1) << m_bitsPerComponent) - static_cast<Value>(1)),
m_buffer(0),
@ -33,33 +34,33 @@ PDFBitReader::PDFBitReader(QDataStream* stream, Value bitsPerComponent) :
Q_ASSERT(bitsPerComponent < 56);
}
PDFBitReader::Value PDFBitReader::read()
PDFBitReader::Value PDFBitReader::read(PDFBitReader::Value bits)
{
while (m_bitsInBuffer < m_bitsPerComponent)
while (m_bitsInBuffer < bits)
{
if (!m_stream->atEnd())
if (m_position < m_stream->size())
{
uint8_t currentByte = 0;
(*m_stream) >> currentByte;
uint8_t currentByte = static_cast<uint8_t>((*m_stream)[m_position++]);
m_buffer = (m_buffer << 8) | currentByte;
m_bitsInBuffer += 8;
}
else
{
throw PDFParserException(PDFTranslationContext::tr("Not enough data to read %1-bit value.").arg(m_bitsPerComponent));
throw PDFParserException(PDFTranslationContext::tr("Not enough data to read %1-bit value.").arg(bits));
}
}
// Now we have enough bits to read the data
Value value = (m_buffer >> (m_bitsInBuffer - m_bitsPerComponent)) & m_maximalValue;
m_bitsInBuffer -= m_bitsPerComponent;
Value value = (m_buffer >> (m_bitsInBuffer - bits)) & ((static_cast<Value>(1) << bits) - static_cast<Value>(1));
m_bitsInBuffer -= bits;
return value;
}
void PDFBitReader::seek(qint64 position)
{
if (m_stream->device()->seek(position))
if (position < m_stream->size())
{
m_position = position;
m_buffer = 0;
m_bitsInBuffer = 0;
}
@ -69,4 +70,13 @@ void PDFBitReader::seek(qint64 position)
}
}
void PDFBitReader::alignToBytes()
{
const Value remainder = m_bitsInBuffer % 8;
if (remainder > 0)
{
read(remainder);
}
}
} // namespace pdf

View File

@ -78,21 +78,29 @@ class PDFBitReader
public:
using Value = uint64_t;
explicit PDFBitReader(QDataStream* stream, Value bitsPerComponent);
explicit PDFBitReader(const QByteArray* stream, Value bitsPerComponent);
/// Returns maximal value of n-bit unsigned integer.
Value max() const { return m_maximalValue; }
/// Reads single n-bit value from the stream. If stream hasn't enough data,
/// then exception is thrown.
Value read();
Value read() { return read(m_bitsPerComponent); }
/// Reads single n-bit value from the stream. If stream hasn't enough data,
/// then exception is thrown.
Value read(Value bits);
/// Seeks the desired position in the data stream. If position can't be seeked,
/// then exception is thrown.
void seek(qint64 position);
/// Seeks data to the byte boundary (number of processed bits is divisible by 8)
void alignToBytes();
private:
QDataStream* m_stream;
const QByteArray* m_stream;
int m_position;
const Value m_bitsPerComponent;
const Value m_maximalValue;
@ -128,6 +136,12 @@ private:
Value* m_value;
};
template<typename T>
bool contains(T value, std::initializer_list<T> list)
{
return (std::find(list.begin(), list.end(), value) != list.end());
}
/// Performs linear mapping of value x in interval [x_min, x_max] to the interval [y_min, y_max].
/// \param x Value to be linearly remapped from interval [x_min, x_max] to the interval [y_min, y_max].
/// \param x_min Start of the input interval