From 084bd3848386e8fa0bea1bb685a605faefe90a49 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 16 May 2017 02:31:56 -0400 Subject: [PATCH 001/152] Fix "Close All Windows" crash --- src/nifskope.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/nifskope.cpp b/src/nifskope.cpp index 43e615976..a706739b6 100644 --- a/src/nifskope.cpp +++ b/src/nifskope.cpp @@ -332,8 +332,10 @@ void NifSkope::exitRequested() FSManager::del(); - if ( options ) + if ( options ) { delete options; + options = nullptr; + } } NifSkope::~NifSkope() From 4ce86c84d5c1af1b6ad6ca20ff9b97bb511f2eb9 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 16 May 2017 02:35:23 -0400 Subject: [PATCH 002/152] Revert back to non-tokens for expression parsing --- src/basemodel.cpp | 2 +- src/kfmmodel.cpp | 2 +- src/nifexpr.cpp | 6 +++--- src/nifmodel.cpp | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/basemodel.cpp b/src/basemodel.cpp index f316b10cd..a20435b3d 100644 --- a/src/basemodel.cpp +++ b/src/basemodel.cpp @@ -663,7 +663,7 @@ NifItem * BaseModel::getItem( NifItem * item, const QString & name ) const if ( !item || item == root ) return nullptr; - int slash = name.indexOf( "/" ); + int slash = name.indexOf( "\\" ); if ( slash > 0 ) { QString left = name.left( slash ); diff --git a/src/kfmmodel.cpp b/src/kfmmodel.cpp index 691da978e..21fb19cc2 100644 --- a/src/kfmmodel.cpp +++ b/src/kfmmodel.cpp @@ -117,7 +117,7 @@ static QString parentPrefix( const QString & x ) { for ( int c = 0; c < x.length(); c++ ) { if ( !x[c].isNumber() ) - return QString( "../" ) + x; + return QString( "..\\" ) + x; } diff --git a/src/nifexpr.cpp b/src/nifexpr.cpp index e462a99ca..03be9b837 100644 --- a/src/nifexpr.cpp +++ b/src/nifexpr.cpp @@ -140,9 +140,9 @@ Expression::Operator Expression::operatorFromString( const QString & str ) return Expression::e_add; else if ( str == "-" ) return Expression::e_sub; - else if ( str == "#DIV#" ) + else if ( str == "/" ) return Expression::e_div; - else if ( str == "#MUL#" ) + else if ( str == "*" ) return Expression::e_mul; else if ( str == "&&" ) return Expression::e_bool_and; @@ -176,7 +176,7 @@ void Expression::partition( const QString & cond, int offset /*= 0*/ ) ostartpos = -1, oendpos = -1, // Operator Start/End rstartpos = -1, rendpos = -1; // Right Start/End - QRegularExpression reOps( "(!=|==|>=|<=|>|<|\\+|-|#DIV#|#MUL#|\\&\\&|\\|\\||\\&|\\|)" ); + QRegularExpression reOps( "(!=|==|>=|<=|>|<|\\+|-|/|\\*|\\&\\&|\\|\\||\\&|\\|)" ); QRegularExpression reLParen( "^\\s*\\(.*" ); QRegularExpressionMatch reLParenMatch = reLParen.match( cond, offset ); diff --git a/src/nifmodel.cpp b/src/nifmodel.cpp index 0b8defc66..bce1954ba 100644 --- a/src/nifmodel.cpp +++ b/src/nifmodel.cpp @@ -390,7 +390,7 @@ NifItem * NifModel::getItem( NifItem * item, const QString & name ) const return nullptr; if ( item->isArray() || item->parent()->isArray() ) { - int slash = name.indexOf( "/" ); + int slash = name.indexOf( "\\" ); if ( slash > 0 ) { QString left = name.left( slash ); QString right = name.right( name.length() - slash - 1 ); @@ -419,7 +419,7 @@ static QString parentPrefix( const QString & x ) { for ( int c = 0; c < x.length(); c++ ) { if ( !x[c].isNumber() ) - return QString( "../" ) + x; + return QString( "..\\" ) + x; } return x; From 2cb4f27ab585a935a528fd51e30ab1344c632105 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 16 May 2017 03:08:09 -0400 Subject: [PATCH 003/152] [bhk] Sub-shape selection for hkPackedNiTriStripsData --- src/gl/glnode.cpp | 42 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/src/gl/glnode.cpp b/src/gl/glnode.cpp index a2a026bf6..fed705478 100644 --- a/src/gl/glnode.cpp +++ b/src/gl/glnode.cpp @@ -747,7 +747,11 @@ void DrawTriangleIndex( QVector const & verts, Triangle const & tri, in void drawHvkShape( const NifModel * nif, const QModelIndex & iShape, QStack & stack, const Scene * scene, const float origin_color3fv[3] ) { - if ( !nif || !iShape.isValid() || stack.contains( iShape ) ) + QString name = nif->itemName( iShape ); + + bool extraData = (name == "hkPackedNiTriStripsData"); + + if ( (!nif || !iShape.isValid() || stack.contains( iShape )) && !extraData ) return; if ( !(scene->selMode & Scene::SelObject) ) @@ -760,8 +764,6 @@ void drawHvkShape( const NifModel * nif, const QModelIndex & iShape, QStackgetBlockNumber( iShape ) << nif->itemName( iShape ); - QString name = nif->itemName( iShape ); - if ( name == "bhkListShape" ) { QModelIndex iShapes = nif->getIndex( iShape, "Sub Shapes" ); @@ -880,7 +882,7 @@ void drawHvkShape( const NifModel * nif, const QModelIndex & iShape, QStackgetBlock( nif->getLink( iShape, "Shape" ) ), stack, scene, origin_color3fv ); - } else if ( name == "bhkPackedNiTriStripsShape" ) { + } else if ( name == "bhkPackedNiTriStripsShape" || name == "hkPackedNiTriStripsData" ) { if ( Node::SELECTING ) { int s_nodeId = ID2COLORKEY( nif->getBlockNumber( iShape ) ); glColor4ubv( (GLubyte *)&s_nodeId ); @@ -940,6 +942,38 @@ void drawHvkShape( const NifModel * nif, const QModelIndex & iShape, QStackget( scene->currentIndex ) ); glEnd(); } + } else if ( n == "Sub Shapes" ) { + int start_vertex = 0; + int end_vertex = 0; + int num_vertices = nif->get( scene->currentIndex, "Num Vertices" ); + + int ct = nif->rowCount( iTris ); + int totalVerts = 0; + if ( num_vertices > 0 ) { + QModelIndex iParent = scene->currentIndex.parent(); + int rowCount = nif->rowCount( iParent ); + for ( int j = 0; j < i; j++ ) { + totalVerts += nif->get( iParent.child( j, 0 ), "Num Vertices" ); + } + + end_vertex += totalVerts + num_vertices; + start_vertex += totalVerts; + + ct = (end_vertex - start_vertex) / 3; + } + + for ( int t = 0; t < nif->rowCount( iTris ); t++ ) { + Triangle tri = nif->get( iTris.child( t, 0 ), "Triangle" ); + + if ( (start_vertex <= tri[0]) && (tri[0] < end_vertex) ) { + if ( (start_vertex <= tri[1]) && (tri[1] < end_vertex) && (start_vertex <= tri[2]) && (tri[2] < end_vertex) ) { + DrawTriangleSelection( verts, tri ); + DrawTriangleIndex( verts, tri, t ); + } else { + qDebug() << "triangle with multiple materials?" << t; + } + } + } } } // Handle Selection of bhkPackedNiTriStripsShape From d3e464cb043525ee1da33eacddf89c21599523d4 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 16 May 2017 03:08:35 -0400 Subject: [PATCH 004/152] [bhk] Display bhkConvexListShape as well --- src/gl/glnode.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gl/glnode.cpp b/src/gl/glnode.cpp index fed705478..a5d563a76 100644 --- a/src/gl/glnode.cpp +++ b/src/gl/glnode.cpp @@ -764,7 +764,7 @@ void drawHvkShape( const NifModel * nif, const QModelIndex & iShape, QStackgetBlockNumber( iShape ) << nif->itemName( iShape ); - if ( name == "bhkListShape" ) { + if ( name.endsWith( "ListShape" ) ) { QModelIndex iShapes = nif->getIndex( iShape, "Sub Shapes" ); if ( iShapes.isValid() ) { From b9fcc1fe98d55856165e908371e08eaa80da848c Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 16 May 2017 03:20:30 -0400 Subject: [PATCH 005/152] [UV] Hold X or Y to constrain axis of motion --- src/widgets/uvedit.cpp | 36 +++++++++++++++++++++++++++++++++++- src/widgets/uvedit.h | 4 ++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/widgets/uvedit.cpp b/src/widgets/uvedit.cpp index cbd5b9f26..79ce31716 100644 --- a/src/widgets/uvedit.cpp +++ b/src/widgets/uvedit.cpp @@ -655,7 +655,15 @@ void UVWidget::mouseMoveEvent( QMouseEvent * e ) } else if ( !selectPoly.isEmpty() ) { selectPoly << e->pos(); } else { - moveSelection( glUnit * zoom * dPos.x(), glUnit * zoom * dPos.y() ); + auto dPosX = glUnit * zoom * dPos.x(); + auto dPosY = glUnit * zoom * dPos.y(); + + if ( kbd[Qt::Key_X] ) + dPosY = 0.0; + if ( kbd[Qt::Key_Y] ) + dPosX = 0.0; + + moveSelection( dPosX, dPosY ); } break; @@ -747,6 +755,32 @@ void UVWidget::wheelEvent( QWheelEvent * e ) updateGL(); } +void UVWidget::keyPressEvent( QKeyEvent * e ) +{ + switch ( e->key() ) { + case Qt::Key_X: + case Qt::Key_Y: + kbd[e->key()] = true; + break; + default: + e->ignore(); + break; + } +} + +void UVWidget::keyReleaseEvent( QKeyEvent * e ) +{ + switch ( e->key() ) { + case Qt::Key_X: + case Qt::Key_Y: + kbd[e->key()] = false; + break; + default: + e->ignore(); + break; + } +} + bool UVWidget::setNifData( NifModel * nifModel, const QModelIndex & nifIndex ) { if ( nif ) { diff --git a/src/widgets/uvedit.h b/src/widgets/uvedit.h index 3630d5296..81a6409f5 100644 --- a/src/widgets/uvedit.h +++ b/src/widgets/uvedit.h @@ -99,6 +99,9 @@ class UVWidget final : public QGLWidget void mouseMoveEvent( QMouseEvent * e ) override final; void wheelEvent( QWheelEvent * e ) override final; + void keyPressEvent( QKeyEvent * ) override final; + void keyReleaseEvent( QKeyEvent * ) override final; + public slots: //! Does the selection contain this vertex? bool isSelected( int index ); @@ -219,6 +222,7 @@ protected slots: QPointF pos; QPoint mousePos; + QHash kbd; GLdouble zoom; From 4b6e025c568471c07b42b33a88a3f0bd317ea6e2 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 16 May 2017 03:20:49 -0400 Subject: [PATCH 006/152] [UV] Remove restrictive window flags --- src/widgets/uvedit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widgets/uvedit.cpp b/src/widgets/uvedit.cpp index 79ce31716..80ffe6e93 100644 --- a/src/widgets/uvedit.cpp +++ b/src/widgets/uvedit.cpp @@ -108,7 +108,7 @@ QStringList UVWidget::texnames = { UVWidget::UVWidget( QWidget * parent ) - : QGLWidget( QGLFormat( QGL::SampleBuffers ), parent, 0, Qt::Tool | Qt::WindowStaysOnTopHint ), undoStack( new QUndoStack( this ) ) + : QGLWidget( QGLFormat( QGL::SampleBuffers ), parent, 0, Qt::Tool ), undoStack( new QUndoStack( this ) ) { setWindowTitle( tr( "UV Editor" ) ); setFocusPolicy( Qt::StrongFocus ); From 9c02f788cce0b7a80348de7aa2e29ad18bb25a10 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 16 May 2017 03:21:31 -0400 Subject: [PATCH 007/152] [GL] Hold spacebar as an alternative to MMB --- src/glview.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/glview.cpp b/src/glview.cpp index 40ecfbef8..a7b66a62d 100644 --- a/src/glview.cpp +++ b/src/glview.cpp @@ -1659,6 +1659,7 @@ void GLView::keyPressEvent( QKeyEvent * event ) //case Qt::Key_F: case Qt::Key_Q: case Qt::Key_E: + case Qt::Key_Space: kbd[event->key()] = true; break; case Qt::Key_Escape: @@ -1692,6 +1693,7 @@ void GLView::keyReleaseEvent( QKeyEvent * event ) //case Qt::Key_F: case Qt::Key_Q: case Qt::Key_E: + case Qt::Key_Space: kbd[event->key()] = false; break; default: @@ -1715,9 +1717,9 @@ void GLView::mouseMoveEvent( QMouseEvent * event ) int dx = event->x() - lastPos.x(); int dy = event->y() - lastPos.y(); - if ( event->buttons() & Qt::LeftButton ) { + if ( event->buttons() & Qt::LeftButton && !kbd[Qt::Key_Space] ) { mouseRot += Vector3( dy * .5, 0, dx * .5 ); - } else if ( event->buttons() & Qt::MidButton ) { + } else if ( (event->buttons() & Qt::MidButton) || (event->buttons() & Qt::LeftButton && kbd[Qt::Key_Space]) ) { float d = axis / (qMax( width(), height() ) + 1); mouseMov += Vector3( dx * d, -dy * d, 0 ); } else if ( event->buttons() & Qt::RightButton ) { From c450727667b75add15f9c92a54281881d04ad651 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 16 May 2017 03:36:35 -0400 Subject: [PATCH 008/152] [GL] Visualize the axis for a node --- src/gl/glnode.cpp | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/gl/glnode.cpp b/src/gl/glnode.cpp index a5d563a76..01c660055 100644 --- a/src/gl/glnode.cpp +++ b/src/gl/glnode.cpp @@ -678,6 +678,29 @@ void Node::drawSelection() const } + if ( currentBlock.endsWith( "Node" ) && scene->options & Scene::ShowNodes && scene->options & Scene::ShowAxes ) { + glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + + Transform t; + Matrix m; + m.fromQuat( nif->get( scene->currentIndex, "Rotation" ) ); + t.rotation = m; + + glPushMatrix(); + glMultMatrix( t ); + + auto pos = Vector3( 0, 0, 0 ); + + glColor( { 0, 1, 0 } ); + drawDashLine( pos, Vector3( 0, 1, 0 ), 15 ); + glColor( { 1, 0, 0 } ); + drawDashLine( pos, Vector3( 1, 0, 0 ), 15 ); + glColor( { 0, 0, 1 } ); + drawDashLine( pos, Vector3( 0, 0, 1 ), 15 ); + + glPopMatrix(); + } + glPopMatrix(); if ( extraData ) From 911985dc7cead1b9f5c382191889039cac916c99 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 16 May 2017 17:31:02 -0400 Subject: [PATCH 009/152] [UI] Row copy/paste incl. multi-row paste Allows copying NifValue of the same type to one or many rows without having to enter the editors for the values with a double-click. Works across windows and is supported by undo/redo. --- src/nifmodel.cpp | 16 +++++ src/nifmodel.h | 5 +- src/nifskope.cpp | 4 ++ src/widgets/nifview.cpp | 144 ++++++++++++++++++++++++++++++++++++++++ src/widgets/nifview.h | 25 +++++++ 5 files changed, 193 insertions(+), 1 deletion(-) diff --git a/src/nifmodel.cpp b/src/nifmodel.cpp index bce1954ba..9cfe292a1 100644 --- a/src/nifmodel.cpp +++ b/src/nifmodel.cpp @@ -3061,6 +3061,22 @@ ChangeValueCommand::ChangeValueCommand( const QModelIndex & index, setText( QApplication::translate( "ChangeValueCommand", "Modify %1" ).arg( valueType ) ); } +ChangeValueCommand::ChangeValueCommand( const QModelIndex & index, const NifValue & oldVal, + const NifValue & newVal, const QString & valueType, NifModel * model ) + : QUndoCommand(), nif( model ), idx( index ) +{ + oldValue = oldVal.toVariant(); + newValue = newVal.toVariant(); + + auto oldTxt = oldVal.toString(); + auto newTxt = newVal.toString(); + + if ( !newTxt.isEmpty() ) + setText( QApplication::translate( "ChangeValueCommand", "Set %1 to %2" ).arg( valueType ).arg( newTxt ) ); + else + setText( QApplication::translate( "ChangeValueCommand", "Modify %1" ).arg( valueType ) ); +} + void ChangeValueCommand::redo() { //qDebug() << "Redoing"; diff --git a/src/nifmodel.h b/src/nifmodel.h index 308507a78..ecad327c7 100644 --- a/src/nifmodel.h +++ b/src/nifmodel.h @@ -395,7 +395,10 @@ class NifModelEval class ChangeValueCommand : public QUndoCommand { public: - ChangeValueCommand( const QModelIndex & index, const QVariant & value, const QString & valueString, const QString & valueType, NifModel * model ); + ChangeValueCommand( const QModelIndex & index, const QVariant & value, + const QString & valueString, const QString & valueType, NifModel * model ); + ChangeValueCommand( const QModelIndex & index, const NifValue & oldValue, + const NifValue & newValue, const QString & valueType, NifModel * model ); void redo() override; void undo() override; private: diff --git a/src/nifskope.cpp b/src/nifskope.cpp index a706739b6..6d235e111 100644 --- a/src/nifskope.cpp +++ b/src/nifskope.cpp @@ -207,6 +207,10 @@ NifSkope::NifSkope() tree->setItemDelegate( nif->createDelegate( this, book ) ); tree->installEventFilter( this ); tree->header()->moveSection( 1, 2 ); + // Allow multi-row paste + // Note: this has some side effects such as vertex selection + // in viewport being wrong if you attempt to select many rows. + tree->setSelectionMode( QAbstractItemView::ExtendedSelection ); // Header Details header = ui->header; diff --git a/src/widgets/nifview.cpp b/src/widgets/nifview.cpp index 5715cc7c2..9b7a9b577 100644 --- a/src/widgets/nifview.cpp +++ b/src/widgets/nifview.cpp @@ -36,6 +36,9 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nifproxy.h" #include "spellbook.h" +#include +#include +#include #include NifTreeView::NifTreeView( QWidget * parent, Qt::WindowFlags flags ) : QTreeView() @@ -130,6 +133,108 @@ QStyleOptionViewItem NifTreeView::viewOptions() const return opt; } +void NifTreeView::copy() +{ + QModelIndex idx = selectionModel()->selectedIndexes().first(); + auto item = static_cast(idx.internalPointer()); + if ( item ) + valueClipboard->setValue( item->value() ); +} + +void NifTreeView::pasteTo( QModelIndex idx ) +{ + NifItem * item = static_cast(idx.internalPointer()); + // Only run once per row for the correct column + if ( idx.column() != NifModel::ValueCol ) + return; + + auto valueType = model()->sibling( idx.row(), 0, idx ).data().toString(); + + auto copyVal = valueClipboard->getValue(); + + NifValue dest = item->value(); + if ( dest.type() != copyVal.type() ) + return; + + switch ( item->value().type() ) { + case NifValue::tByte: + nif->set( idx, copyVal.get() ); + break; + case NifValue::tWord: + case NifValue::tShort: + case NifValue::tFlags: + case NifValue::tBlockTypeIndex: + nif->set( idx, copyVal.get() ); + break; + case NifValue::tStringOffset: + case NifValue::tInt: + case NifValue::tUInt: + case NifValue::tULittle32: + case NifValue::tStringIndex: + case NifValue::tUpLink: + case NifValue::tLink: + nif->set( idx, copyVal.get() ); + break; + case NifValue::tVector2: + case NifValue::tHalfVector2: + nif->set( idx, copyVal.get() ); + break; + case NifValue::tVector3: + case NifValue::tByteVector3: + case NifValue::tHalfVector3: + nif->set( idx, copyVal.get() ); + break; + case NifValue::tVector4: + nif->set( idx, copyVal.get() ); + break; + case NifValue::tFloat: + case NifValue::tHfloat: + nif->set( idx, copyVal.get() ); + break; + case NifValue::tColor3: + nif->set( idx, copyVal.get() ); + break; + case NifValue::tColor4: + case NifValue::tByteColor4: + nif->set( idx, copyVal.get() ); + break; + case NifValue::tQuat: + case NifValue::tQuatXYZW: + nif->set( idx, copyVal.get() ); + break; + case NifValue::tMatrix: + nif->set( idx, copyVal.get() ); + break; + case NifValue::tMatrix4: + nif->set( idx, copyVal.get() ); + break; + case NifValue::tString: + case NifValue::tSizedString: + case NifValue::tText: + case NifValue::tShortString: + case NifValue::tHeaderString: + case NifValue::tLineString: + case NifValue::tChar8String: + nif->set( idx, copyVal.get() ); + break; + default: + // Return and do not push to Undo Stack + return; + } + + auto n = static_cast(nif); + if ( n ) + n->undoStack->push( new ChangeValueCommand( idx, dest, valueClipboard->getValue(), valueType, n ) ); +} + +void NifTreeView::paste() +{ + QModelIndexList idx = selectionModel()->selectedIndexes(); + for ( const auto i : idx ) { + pasteTo( i ); + } +} + void NifTreeView::drawBranches( QPainter * painter, const QRect & rect, const QModelIndex & index ) const { if ( rootIsDecorated() ) @@ -163,8 +268,43 @@ void NifTreeView::updateConditionRecurse( const QModelIndex & index ) setRowHidden( index.row(), index.parent(), doRowHiding && !item->condition() ); } +auto splitMime = []( QString format ) { + QStringList split = format.split( "/" ); + if ( split.value( 0 ) == "nifskope" + && (split.value( 1 ) == "niblock" || split.value( 1 ) == "nibranch") ) + return !split.value( 2 ).isEmpty(); +}; + void NifTreeView::keyPressEvent( QKeyEvent * e ) { + auto details = model()->inherits( "NifModel" ); + if ( details ) { + // Determine if a block or branch has been copied + bool hasBlockCopied = false; + if ( e->matches( QKeySequence::Copy ) || e->matches( QKeySequence::Paste ) ) { + auto mime = QApplication::clipboard()->mimeData(); + if ( mime ) { + for ( const QString& form : mime->formats() ) { + if ( splitMime( form ) ) { + hasBlockCopied = true; + break; + } + } + } + } + + if ( e->matches( QKeySequence::Copy ) ) { + copy(); + // Clear the clipboard in case it holds a block to prevent conflicting behavior + QApplication::clipboard()->clear(); + return; + } else if ( e->matches( QKeySequence::Paste ) && valueClipboard->getValue().isValid() && !hasBlockCopied ) { + // Do row paste if there is no block/branch copied and the NifValue is valid + paste(); + return; + } + } + SpellPtr spell = SpellBook::lookup( QKeySequence( e->modifiers() + e->key() ) ); if ( spell ) { @@ -173,6 +313,10 @@ void NifTreeView::keyPressEvent( QKeyEvent * e ) QPersistentModelIndex oldidx; + // Clear this on any spell cast to prevent it overriding other paste behavior like block -> link row + // TODO: Value clipboard does not get cleared when using the context menu. + valueClipboard->getValue().clear(); + if ( model()->inherits( "NifModel" ) ) { nif = static_cast( model() ); oldidx = currentIndex(); diff --git a/src/widgets/nifview.h b/src/widgets/nifview.h index 328b513cf..13b9d01f2 100644 --- a/src/widgets/nifview.h +++ b/src/widgets/nifview.h @@ -34,6 +34,9 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define NIFTREEVIEW_H #include // Inherited +#include + +#include //! Widget for showing a nif file as tree, list, or block details. @@ -95,7 +98,29 @@ protected slots: bool doRowHiding = true; class BaseModel * nif = nullptr; + + //! Row Copy + void copy(); + //! Row Paste + void paste(); + void pasteTo( QModelIndex idx ); + +}; + + +//! A global clipboard for NifTreeView to store a NifValue for all windows +class NifValueClipboard +{ + +public: + NifValue getValue() { return value; } + void setValue( NifValue val ) { value = val; } + +private: + NifValue value = NifValue(); }; +// The global NifTreeView clipboard pointer +static auto valueClipboard = std::make_unique(); #endif From ffa27621ec042129be19266e57d530a27e411b40 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 16 May 2017 18:07:42 -0400 Subject: [PATCH 010/152] [BSA] Cleanup sized string read --- lib/fsengine/bsa.cpp | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/lib/fsengine/bsa.cpp b/lib/fsengine/bsa.cpp index 115a78584..69a90431b 100644 --- a/lib/fsengine/bsa.cpp +++ b/lib/fsengine/bsa.cpp @@ -64,27 +64,17 @@ bool BSA::BSAFile::compressed() const //! Reads a foldername sized string (length + null-terminated string) from the BSA static bool BSAReadSizedString( QFile & bsa, QString & s ) { - //qDebug() << "BSA is at" << bsa.pos(); quint8 len; - if ( bsa.read( (char *) & len, 1 ) != 1 ) - { - //qDebug() << "bailout on" << __FILE__ << "line" << __LINE__; + if ( bsa.read( (char *)&len, 1 ) != 1 ) return false; - } - //qDebug() << "folder string length is" << len; QByteArray b( len, char(0) ); - if ( bsa.read( b.data(), len ) == len ) - { + if ( bsa.read( b.data(), len ) == len ) { s = QString::fromLatin1( b ); - //qDebug() << "bailout on" << __FILE__ << "line" << __LINE__; return true; } - else - { - //qDebug() << "bailout on" << __FILE__ << "line" << __LINE__; - return false; - } + + return false; } QByteArray gUncompress( const QByteArray & data, const int size ) From 585c3b359e1f2f047cda3cdfbc3ff0b065ed9103 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 16 May 2017 18:24:24 -0400 Subject: [PATCH 011/152] [Spell] NiControllerSequence filling NiControllerSequence has "Controller Type" rows that are exported blank for KF files by the exporters. This is a spell done by request to fill the rows with a type to prevent the game from crashing. This is not an auto-sanitizer. --- src/spells/sanitize.cpp | 76 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/src/spells/sanitize.cpp b/src/spells/sanitize.cpp index a073a01cd..ecfc4dabc 100644 --- a/src/spells/sanitize.cpp +++ b/src/spells/sanitize.cpp @@ -1,6 +1,8 @@ #include "spellbook.h" #include "misc.h" +#include + #include // std::stable_sort @@ -508,3 +510,77 @@ class spFixInvalidNames final : public Spell }; REGISTER_SPELL( spFixInvalidNames ) + +//! Fills blank "Controller Type" refs in NiControllerSequence +class spFillBlankControllerTypes final : public Spell +{ +public: + QString name() const override final { return Spell::tr( "Fill Blank NiControllerSequence Types" ); } + QString page() const override final { return Spell::tr( "Sanitize" ); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final + { + return nif && nif->getIndex( nif->getHeader(), "Num Strings" ).isValid() && !index.isValid(); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & ) override final + { + QVector stringsToAdd; + QVector modifiedNames; + + auto iHeader = nif->getHeader(); + auto numStrings = nif->get( iHeader, "Num Strings" ); + auto strings = nif->getArray( iHeader, "Strings" ); + + bool ok = true; + QString str = QInputDialog::getText( 0, Spell::tr( "Fill Blank NiControllerSequence Types" ), + Spell::tr( "Choose the default Controller Type" ), + QLineEdit::Normal, "NiTransformController", &ok ); + + if ( !ok ) + return QModelIndex(); + + auto stringIdx = strings.indexOf( str ); + if ( stringIdx == -1 ) { + // Append new strings to header strings + strings << str; + stringIdx = numStrings; + } + + for ( int i = 0; i < nif->getBlockCount(); i++ ) { + QModelIndex iBlock = nif->getBlock( i ); + if ( !(nif->inherits( iBlock, "NiControllerSequence" )) ) + continue; + + auto controlledBlocks = nif->getIndex( iBlock, "Controlled Blocks" ); + auto numBlocks = nif->rowCount( controlledBlocks ); + + for ( int i = 0; i < numBlocks; i++ ) { + auto ctrlrType = nif->getIndex( controlledBlocks.child( i, 0 ), "Controller Type" ); + auto nodeName = nif->getIndex( controlledBlocks.child( i, 0 ), "Node Name" ); + + auto ctrlrTypeIdx = nif->get( ctrlrType ); + if ( ctrlrTypeIdx == -1 ) { + nif->set( ctrlrType, stringIdx ); + modifiedNames << nif->get( nodeName ); + } + } + } + + // Update header + nif->set( iHeader, "Num Strings", strings.count() ); + nif->updateArray( iHeader, "Strings" ); + nif->setArray( iHeader, "Strings", strings ); + + nif->updateHeader(); + + for ( const QString& s : modifiedNames ) { + Message::append( Spell::tr( "One or more NiControllerSequence rows have been sanitized" ), + QString( "%1" ).arg( s ) ); + } + + return QModelIndex(); + } +}; + +REGISTER_SPELL( spFillBlankControllerTypes ) From 59b9b928dff2e2db8dad2f2c8a78fffa6cf05e18 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 16 May 2017 18:28:24 -0400 Subject: [PATCH 012/152] [UI] Helper literal to separate debug/release UI --- src/nifskope.h | 5 +++++ src/nifskope_ui.cpp | 47 ++++++++++++++++++++++++++++++--------------- 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/src/nifskope.h b/src/nifskope.h index ec6059d38..7008625df 100644 --- a/src/nifskope.h +++ b/src/nifskope.h @@ -53,6 +53,11 @@ namespace Ui { class MainWindow; } +namespace nstypes +{ + QString operator"" _uip( const char * str, size_t sz ); +} + class FileSelector; class GLView; class GLGraphicsView; diff --git a/src/nifskope_ui.cpp b/src/nifskope_ui.cpp index 8cfb20b20..4bf25e0e8 100644 --- a/src/nifskope_ui.cpp +++ b/src/nifskope_ui.cpp @@ -77,6 +77,21 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +QString nstypes::operator""_uip( const char * str, size_t ) +{ + QString u; + +#ifndef QT_NO_DEBUG + u = "UI/Debug/"; +#else + u = "UI/"; +#endif + + return u + QString( str ); +} + +using namespace nstypes; + //! @file nifskope_ui.cpp UI logic for %NifSkope's main window. @@ -988,18 +1003,18 @@ void NifSkope::saveUi() const { QSettings settings; // TODO: saveState takes a version number which can be incremented between releases if necessary - settings.setValue( "UI/Window State", saveState( 0x073 ) ); - settings.setValue( "UI/Window Geometry", saveGeometry() ); + settings.setValue( "Window State"_uip, saveState( 0x073 ) ); + settings.setValue( "Window Geometry"_uip, saveGeometry() ); settings.setValue( "File/Auto Sanitize", aSanitize->isChecked() ); - settings.setValue( "UI/List Mode", (gListMode->checkedAction() == aList ? "list" : "hierarchy") ); - settings.setValue( "UI/Show Non-applicable Rows", aCondition->isChecked() ); + settings.setValue( "List Mode"_uip, (gListMode->checkedAction() == aList ? "list" : "hierarchy") ); + settings.setValue( "Show Non-applicable Rows"_uip, aCondition->isChecked() ); - settings.setValue( "UI/List Header", list->header()->saveState() ); - settings.setValue( "UI/Tree Header", tree->header()->saveState() ); - settings.setValue( "UI/Header Header", header->header()->saveState() ); - settings.setValue( "UI/Kfmtree Header", kfmtree->header()->saveState() ); + settings.setValue( "List Header"_uip, list->header()->saveState() ); + settings.setValue( "Tree Header"_uip, tree->header()->saveState() ); + settings.setValue( "Header Header"_uip, header->header()->saveState() ); + settings.setValue( "Kfmtree Header"_uip, kfmtree->header()->saveState() ); settings.setValue( "GLView/Enable Animations", ui->aAnimate->isChecked() ); //settings.setValue( "GLView/Play Animation", ui->aAnimPlay->isChecked() ); @@ -1012,24 +1027,24 @@ void NifSkope::saveUi() const void NifSkope::restoreUi() { QSettings settings; - restoreGeometry( settings.value( "UI/Window Geometry" ).toByteArray() ); - restoreState( settings.value( "UI/Window State" ).toByteArray(), 0x073 ); + restoreGeometry( settings.value( "Window Geometry"_uip ).toByteArray() ); + restoreState( settings.value( "Window State"_uip ).toByteArray(), 0x073 ); aSanitize->setChecked( settings.value( "File/Auto Sanitize", true ).toBool() ); - if ( settings.value( "UI/List Mode", "hierarchy" ).toString() == "list" ) + if ( settings.value( "List Mode"_uip, "hierarchy" ).toString() == "list" ) aList->setChecked( true ); else aHierarchy->setChecked( true ); setListMode(); - aCondition->setChecked( settings.value( "UI/Show Non-applicable Rows", false ).toBool() ); + aCondition->setChecked( settings.value( "Show Non-applicable Rows"_uip, false ).toBool() ); - list->header()->restoreState( settings.value( "UI/List Header" ).toByteArray() ); - tree->header()->restoreState( settings.value( "UI/Tree Header" ).toByteArray() ); - header->header()->restoreState( settings.value( "UI/Header Header" ).toByteArray() ); - kfmtree->header()->restoreState( settings.value( "UI/Kfmtree Header" ).toByteArray() ); + list->header()->restoreState( settings.value( "List Header"_uip ).toByteArray() ); + tree->header()->restoreState( settings.value( "Tree Header"_uip ).toByteArray() ); + header->header()->restoreState( settings.value( "Header Header"_uip ).toByteArray() ); + kfmtree->header()->restoreState( settings.value( "Kfmtree Header"_uip ).toByteArray() ); auto hideSections = []( NifTreeView * tree, bool hidden ) { tree->header()->setSectionHidden( NifModel::ArgCol, hidden ); From 702bbe5d87098a44626ecc69d7378b2304cfd178 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 16 May 2017 20:20:24 -0400 Subject: [PATCH 013/152] [XML] Add BTO and BTR files to checker --- src/widgets/xmlcheck.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widgets/xmlcheck.cpp b/src/widgets/xmlcheck.cpp index 3ba5010d2..b5489e2f8 100644 --- a/src/widgets/xmlcheck.cpp +++ b/src/widgets/xmlcheck.cpp @@ -207,7 +207,7 @@ void TestShredder::run() QStringList extensions; if ( chkNif->isChecked() ) - extensions << "*.nif" << "*.nifcache" << "*.texcache" << "*.pcpatch"; + extensions << "*.nif" << "*.nifcache" << "*.texcache" << "*.pcpatch" << "*.bto" << "*.btr"; if ( chkKf->isChecked() ) extensions << "*.kf" << "*.kfa"; if ( chkKfm->isChecked() ) From a73d9e896ad94a1a02f0efe1deb4f1a6f3e6b0b7 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 16 May 2017 20:21:16 -0400 Subject: [PATCH 014/152] Disable migrations for debug builds --- src/nifskope.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/nifskope.cpp b/src/nifskope.cpp index 6d235e111..9c316cadb 100644 --- a/src/nifskope.cpp +++ b/src/nifskope.cpp @@ -1518,6 +1518,7 @@ void NifSkope::migrateSettings() const } } +#ifdef QT_NO_DEBUG // Check Qt Version if ( curQtVer != prevQtVer ) { // Check all keys and delete all QByteArrays @@ -1534,4 +1535,5 @@ void NifSkope::migrateSettings() const cfg.setValue( "Qt Version", curQtVer ); } +#endif } From 1bbda49c45f313119262e109acd3b2c1a002f9d0 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 16 May 2017 20:32:59 -0400 Subject: [PATCH 015/152] Fix README formatting --- README.md | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 69525d362..b4fe6f239 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,14 @@ -================= - NifSkope 2.0.dev6 -================= +### NifSkope 2.0.dev6 NifSkope is a tool for opening and editing the NetImmerse file format (NIF). NIF is used by video games such as Morrowind, Oblivion, Skyrim, Fallout 3, Fallout: New Vegas, Civilization IV, and more. -Download ------------------ +#### Download You can download the latest official release from [Sourceforge](https://sourceforge.net/projects/niftools/files/nifskope/). -Discussion & Help ------------------ +#### Discussion & Help + Visit our forum at [NifTools.org](http://niftools.sourceforge.net/forum). To receive support for NifSkope please use the [NifSkope Help subforum](http://niftools.sourceforge.net/forum/viewforum.php?f=24). @@ -20,8 +17,7 @@ Visit our forum at [NifTools.org](http://niftools.sourceforge.net/forum). To rec Anyone can [report issues at GitHub](https://github.com/niftools/nifskope/issues) or in the [Bug Reports subforum](http://niftools.sourceforge.net/forum/viewforum.php?f=24) at NifTools.org. -Contribute ------------------ +#### Contribute You can fork the latest source from [GitHub](https://github.com/niftools/nifskope). See [Fork A Repo](https://help.github.com/articles/fork-a-repo) on how to send your contributions upstream. To grab all submodules, make sure to use `--recursive` like so: @@ -35,8 +31,7 @@ For information about development: - Refer to our [GitHub wiki](https://github.com/niftools/nifskope/wiki#wiki-development) for information on compilation. -Miscellaneous ------------------ +#### Miscellaneous Refer to these other documents in your installation folder or at the links provided: From 42d753ef59ba931294aaad475b350a65f10ddc68 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 16 May 2017 20:34:30 -0400 Subject: [PATCH 016/152] Fix README formatting 2 --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index b4fe6f239..a78915c8f 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,23 @@ -### NifSkope 2.0.dev6 +# NifSkope 2.0.dev6 NifSkope is a tool for opening and editing the NetImmerse file format (NIF). NIF is used by video games such as Morrowind, Oblivion, Skyrim, Fallout 3, Fallout: New Vegas, Civilization IV, and more. -#### Download +### Download You can download the latest official release from [Sourceforge](https://sourceforge.net/projects/niftools/files/nifskope/). -#### Discussion & Help +### Discussion & Help Visit our forum at [NifTools.org](http://niftools.sourceforge.net/forum). To receive support for NifSkope please use the [NifSkope Help subforum](http://niftools.sourceforge.net/forum/viewforum.php?f=24). -#### Issues +### Issues Anyone can [report issues at GitHub](https://github.com/niftools/nifskope/issues) or in the [Bug Reports subforum](http://niftools.sourceforge.net/forum/viewforum.php?f=24) at NifTools.org. -#### Contribute +### Contribute You can fork the latest source from [GitHub](https://github.com/niftools/nifskope). See [Fork A Repo](https://help.github.com/articles/fork-a-repo) on how to send your contributions upstream. To grab all submodules, make sure to use `--recursive` like so: @@ -31,14 +31,14 @@ For information about development: - Refer to our [GitHub wiki](https://github.com/niftools/nifskope/wiki#wiki-development) for information on compilation. -#### Miscellaneous +### Miscellaneous Refer to these other documents in your installation folder or at the links provided: -### [TROUBLESHOOTING](https://github.com/niftools/nifskope/blob/develop/TROUBLESHOOTING.md) +## [TROUBLESHOOTING](https://github.com/niftools/nifskope/blob/develop/TROUBLESHOOTING.md) -### [CHANGELOG](https://github.com/niftools/nifskope/blob/develop/CHANGELOG.md) +## [CHANGELOG](https://github.com/niftools/nifskope/blob/develop/CHANGELOG.md) -### [CONTRIBUTORS](https://github.com/niftools/nifskope/blob/develop/CONTRIBUTORS.md) +## [CONTRIBUTORS](https://github.com/niftools/nifskope/blob/develop/CONTRIBUTORS.md) -### [LICENSE](https://github.com/niftools/nifskope/blob/develop/LICENSE.md) +## [LICENSE](https://github.com/niftools/nifskope/blob/develop/LICENSE.md) From 5031542ec7a43a4a3d55bf17db3d03e7f1c70e55 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Wed, 17 May 2017 00:25:07 -0400 Subject: [PATCH 017/152] Fix README formatting 3 Forgot the .md.in --- build/README.md.in | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/build/README.md.in b/build/README.md.in index fb46d5364..9a5ab8505 100644 --- a/build/README.md.in +++ b/build/README.md.in @@ -1,27 +1,23 @@ -================= - NifSkope @VERSION@ -================= +# NifSkope @VERSION@ NifSkope is a tool for opening and editing the NetImmerse file format (NIF). NIF is used by video games such as Morrowind, Oblivion, Skyrim, Fallout 3, Fallout: New Vegas, Civilization IV, and more. -Download ------------------ +### Download You can download the latest official release from [Sourceforge](https://sourceforge.net/projects/niftools/files/nifskope/). -Discussion & Help ------------------ +### Discussion & Help + Visit our forum at [NifTools.org](http://niftools.sourceforge.net/forum). To receive support for NifSkope please use the [NifSkope Help subforum](http://niftools.sourceforge.net/forum/viewforum.php?f=24). -#### Issues +### Issues Anyone can [report issues at GitHub](https://github.com/niftools/nifskope/issues) or in the [Bug Reports subforum](http://niftools.sourceforge.net/forum/viewforum.php?f=24) at NifTools.org. -Contribute ------------------ +### Contribute You can fork the latest source from [GitHub](https://github.com/niftools/nifskope). See [Fork A Repo](https://help.github.com/articles/fork-a-repo) on how to send your contributions upstream. To grab all submodules, make sure to use `--recursive` like so: @@ -35,15 +31,15 @@ For information about development: - Refer to our [GitHub wiki](https://github.com/niftools/nifskope/wiki#wiki-development) for information on compilation. -Miscellaneous ------------------ +### Miscellaneous Refer to these other documents in your installation folder or at the links provided: -### [TROUBLESHOOTING](https://github.com/niftools/nifskope/blob/develop/TROUBLESHOOTING.md) +## [TROUBLESHOOTING](https://github.com/niftools/nifskope/blob/develop/TROUBLESHOOTING.md) -### [CHANGELOG](https://github.com/niftools/nifskope/blob/develop/CHANGELOG.md) +## [CHANGELOG](https://github.com/niftools/nifskope/blob/develop/CHANGELOG.md) -### [CONTRIBUTORS](https://github.com/niftools/nifskope/blob/develop/CONTRIBUTORS.md) +## [CONTRIBUTORS](https://github.com/niftools/nifskope/blob/develop/CONTRIBUTORS.md) -### [LICENSE](https://github.com/niftools/nifskope/blob/develop/LICENSE.md) +## [LICENSE](https://github.com/niftools/nifskope/blob/develop/LICENSE.md) + From 7d63659215af96577a0c99c136ffaafec8722f1f Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sat, 27 May 2017 22:54:41 -0400 Subject: [PATCH 018/152] [Build] Updates for 5.8+ and misc fixes After 5.8 qdds plugin no longer exists. It was not used anyway. Also corrected and simplified mkdir behavior and made sure to quote the output of `where sed 2> NUL` if it's actually on the path in Windows. --- NifSkope.pro | 1 - NifSkope_functions.pri | 14 ++++++++------ NifSkope_targets.pri | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/NifSkope.pro b/NifSkope.pro index cf1278059..558f3ad42 100644 --- a/NifSkope.pro +++ b/NifSkope.pro @@ -567,7 +567,6 @@ build_pass|!debug_and_release { $$[QT_INSTALL_PLUGINS]/platforms/qwindows$${DLLEXT} imageformats += \ - $$[QT_INSTALL_PLUGINS]/imageformats/qdds$${DLLEXT} \ $$[QT_INSTALL_PLUGINS]/imageformats/qjpeg$${DLLEXT} \ $$[QT_INSTALL_PLUGINS]/imageformats/qtga$${DLLEXT} \ $$[QT_INSTALL_PLUGINS]/imageformats/qwebp$${DLLEXT} diff --git a/NifSkope_functions.pri b/NifSkope_functions.pri index e13d0b4e5..42da7b9d8 100644 --- a/NifSkope_functions.pri +++ b/NifSkope_functions.pri @@ -29,13 +29,17 @@ defineReplace(getSed) { SEDPATH = /sed.exe exists($${GNUWIN32}$${SEDPATH}) { - sedbin = \"$${GNUWIN32}$${SEDPATH}\" + sedbin = $${GNUWIN32}$${SEDPATH} } else:exists($${CYGWIN}$${SEDPATH}) { - sedbin = \"$${CYGWIN}$${SEDPATH}\" + sedbin = $${CYGWIN}$${SEDPATH} } else { #message(Neither GnuWin32 or Cygwin were found) sedbin = $$system(where sed 2> NUL) } + + !isEmpty(sedbin) { + sedbin = \"$${sedbin}\" + } } unix { @@ -200,8 +204,7 @@ defineTest(copyFiles) { } ddir = $$syspath($${DESTDIR}$${QMAKE_DIR_SEP}$${subdir}) - unix:QMAKE_POST_LINK += $$QMAKE_MKDIR_CMD $${ddir} $$nt - else:QMAKE_POST_LINK += $$QMAKE_CHK_DIR_EXISTS $${ddir} $$QMAKE_MKDIR $${ddir} $$nt + QMAKE_POST_LINK += $$sprintf($$QMAKE_MKDIR_CMD, $${ddir}) $$nt for(FILE, files) { fileabs = $${PWD}$${QMAKE_DIR_SEP}$${FILE} @@ -241,8 +244,7 @@ defineTest(copyDirs) { } ddir = $$syspath($${DESTDIR}$${QMAKE_DIR_SEP}$${subdir}) - unix:QMAKE_POST_LINK += $$QMAKE_MKDIR_CMD $${ddir} $$nt - else:QMAKE_POST_LINK += $$QMAKE_CHK_DIR_EXISTS $${ddir} $$QMAKE_MKDIR $${ddir} $$nt + QMAKE_POST_LINK += $$sprintf($$QMAKE_MKDIR_CMD, $${ddir}) $$nt for(DIR, dirs) { dirabs = $${PWD}$${QMAKE_DIR_SEP}$${DIR} diff --git a/NifSkope_targets.pri b/NifSkope_targets.pri index ce93ef117..61c8e9250 100644 --- a/NifSkope_targets.pri +++ b/NifSkope_targets.pri @@ -61,7 +61,7 @@ outdoc = $$syspath($${DESTDIR}/doc) # COMMANDS -docs.commands += $${QMAKE_CHK_DIR_EXISTS} $${outdoc} $${QMAKE_MKDIR} $${outdoc} $$nt +docs.commands += $$sprintf($$QMAKE_MKDIR_CMD, $${outdoc}) $$nt docs.commands += cd $${docsys} $$nt # cd ./build/docsys docs.commands += python nifxml_doc.py $$nt # invoke python # Move *.html files out of ./build/docsys/doc From ab032ac6dc4e560d8ae5eedc410f3db287ad0b21 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sat, 27 May 2017 22:55:05 -0400 Subject: [PATCH 019/152] [Build] Require C++14, VS2015+, Qt 5.7+ Upped requirements for upcoming changes that necessitate C++14 support. Also removed SSE2 flag because of 64-bit builds not needing it. --- NifSkope.pro | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/NifSkope.pro b/NifSkope.pro index 558f3ad42..2ffe254f3 100644 --- a/NifSkope.pro +++ b/NifSkope.pro @@ -7,14 +7,14 @@ TARGET = NifSkope QT += xml opengl network widgets -# Require Qt 5.5 or higher -contains(QT_VERSION, ^5\\.[0-4]\\..*) { +# Require Qt 5.7 or higher +contains(QT_VERSION, ^5\\.[0-6]\\..*) { message("Cannot build NifSkope with Qt version $${QT_VERSION}") - error("Minimum required version is Qt 5.5") + error("Minimum required version is Qt 5.7") } -# C++11 Support -CONFIG += c++11 +# C++11/14 Support +CONFIG += c++14 # Dependencies CONFIG += nvtristrip qhull soil zlib lz4 fsengine @@ -433,14 +433,14 @@ win32 { *msvc* { # Grab _MSC_VER from the mkspecs that Qt was compiled with - # e.g. VS2013 = 1800, VS2012 = 1700, VS2010 = 1600 + # e.g. VS2015 = 1900, VS2017 = 1910 _MSC_VER = $$find(QMAKE_COMPILER_DEFINES, "_MSC_VER") _MSC_VER = $$split(_MSC_VER, =) _MSC_VER = $$member(_MSC_VER, 1) # Reject unsupported MSVC versions - !isEmpty(_MSC_VER):lessThan(_MSC_VER, 1800) { - error("NifSkope only supports MSVC 2013 or later. If this is too prohibitive you may use Qt Creator with MinGW.") + !isEmpty(_MSC_VER):lessThan(_MSC_VER, 1900) { + error("NifSkope only supports MSVC 2015 or later. If this is too prohibitive you may use Qt Creator with MinGW.") } # So VCProj Filters do not flatten headers/source @@ -449,7 +449,7 @@ win32 { # COMPILER FLAGS # Optimization flags - QMAKE_CXXFLAGS_RELEASE *= -O2 -arch:SSE2 # SSE2 is the default, but make it explicit + QMAKE_CXXFLAGS_RELEASE *= -O2 # Multithreaded compiling for Visual Studio QMAKE_CXXFLAGS += -MP @@ -479,7 +479,7 @@ win32 { QMAKE_CXXFLAGS_RELEASE *= -O3 -mfpmath=sse # C++11 Support - QMAKE_CXXFLAGS_RELEASE *= -std=c++11 + QMAKE_CXXFLAGS_RELEASE *= -std=c++14 # Extension flags QMAKE_CXXFLAGS_RELEASE *= -msse2 -msse From b98b84914534fcd206fed8651555e444f490aab0 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Fri, 9 Jun 2017 06:56:27 -0400 Subject: [PATCH 020/152] Ignore Block Size by default Will need to change this later to have an actual setting in the UI for it, and possible warning message when the sizes are incorrect. --- src/nifmodel.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/nifmodel.cpp b/src/nifmodel.cpp index 9cfe292a1..96649cf92 100644 --- a/src/nifmodel.cpp +++ b/src/nifmodel.cpp @@ -1717,8 +1717,7 @@ bool NifModel::setHeaderString( const QString & s ) bool NifModel::load( QIODevice & device ) { QSettings cfg; - bool ignoreSize = false; - ignoreSize = cfg.value( "Ignore Block Size", false ).toBool(); + bool ignoreSize = cfg.value( "Ignore Block Size", true ).toBool(); clear(); From 07e92b682279b9da7d76cdda052ec24bb86aa517 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Fri, 9 Jun 2017 06:57:24 -0400 Subject: [PATCH 021/152] [Spell] Fix None refs in NiDefaultAVObjectPalette --- src/spells/animation.cpp | 66 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/src/spells/animation.cpp b/src/spells/animation.cpp index 706016fdb..6e053775f 100644 --- a/src/spells/animation.cpp +++ b/src/spells/animation.cpp @@ -364,3 +364,69 @@ class spConvertQuatsToEulers final : public Spell //REGISTER_SPELL( spConvertQuatsToEulers ) + +class spFixAVObjectPalette final : public Spell +{ +public: + QString name() const override final { return Spell::tr( "Fix Invalid AV Object Refs" ); } + QString page() const override final { return Spell::tr( "Animation" ); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final + { + QModelIndex iBlock = nif->getBlock( index, "NiDefaultAVObjectPalette" ); + return iBlock.isValid(); + } + + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + auto iHeader = nif->getHeader(); + auto numStrings = nif->get( iHeader, "Num Strings" ); + auto strings = nif->getArray( iHeader, "Strings" ); + + auto numBlocks = nif->get( iHeader, "Num Blocks" ); + + int fixed = 0; + int invalid = 0; + + auto objs = nif->getIndex( index, "Objs" ); + auto numObjs = nif->rowCount( objs ); + for ( int i = 0; i < numObjs; i++ ) { + auto c = objs.child( i, 0 ); + auto iAV = nif->getIndex( c, "AV Object" ); + + + auto av = nif->getLink( iAV ); + if ( av == -1 ) { + invalid++; + + auto name = nif->get( c, "Name" ); + auto stringIndex = strings.indexOf( name ); + if ( stringIndex >= 0 ) { + for ( int j = 0; j < numBlocks; j++ ) { + auto iBlock = nif->getBlock( j ); + + if ( nif->inherits( iBlock, "NiAVObject" ) ) { + auto blockName = nif->get( nif->getBlock( j ), "Name" ); + if ( name == blockName ) { + nif->setLink( iAV, j ); + fixed++; + invalid--; + break; + } + } + } + } + } + } + + if ( fixed > 0 ) { + Message::info( nullptr, Spell::tr( "Fixed %1 AV Object Refs, %2 invalid Refs remain." ) + .arg( fixed ).arg( invalid ) ); + } + + return {}; + } +}; + +REGISTER_SPELL( spFixAVObjectPalette ) From 480551e9431a1b5565e401e583c800f0c440618f Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 23 May 2017 07:16:18 -0400 Subject: [PATCH 022/152] [NifModel] Introduce mixins (specialized compounds) A series of XML updates from ttl269 broke many features of the program because they introduced unneeded nesting. Their use was solely to reduce repetition and not to show hierarchy so I am now treating them as if they are includes. This works almost identically to the way abstract niobject work except the mixin can be anywhere in the block and not only the front. I will likely propose this be added to the XML itself as it denotes usage of the compound. --- src/nifitem.h | 7 ++++++- src/nifmodel.cpp | 11 +++++++++-- src/nifxml.cpp | 34 +++++++++++++++++++++++++++++----- 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/nifitem.h b/src/nifitem.h index 1c64ba151..168a7d13c 100644 --- a/src/nifitem.h +++ b/src/nifitem.h @@ -62,7 +62,8 @@ class NifSharedData final : public QSharedData Compound = 0x8, Array = 0x10, MultiArray = 0x20, - Conditionless = 0x40 + Conditionless = 0x40, + Mixin = 0x80 }; typedef QFlags DataFlags; @@ -177,6 +178,8 @@ class NifData inline bool isMultiArray() const { return d->flags & NifSharedData::MultiArray; } //! Is the data conditionless. Conditionless means no expression evaluation is necessary. inline bool isConditionless() const { return d->flags & NifSharedData::Conditionless; } + //! Is the data a mixin. Mixin is a specialized compound which creates no nesting. + inline bool isMixin() const { return d->flags & NifSharedData::Mixin; } //! Sets the name of the data. void setName( const QString & name ) { d->name = name; } @@ -231,6 +234,8 @@ class NifData inline void setIsMultiArray( bool flag ) { setFlag( NifSharedData::MultiArray, flag ); } //! Sets the conditionless data flag. Conditionless means no expression evaluation is necessary. inline void setIsConditionless( bool flag ) { setFlag( NifSharedData::Conditionless, flag ); } + //! Sets the mixin data flag. Mixin is a specialized compound which creates no nesting. + inline void setIsMixin( bool flag ) { setFlag( NifSharedData::Mixin, flag ); } protected: //! The internal shared data. diff --git a/src/nifmodel.cpp b/src/nifmodel.cpp index 96649cf92..5b11d7c49 100644 --- a/src/nifmodel.cpp +++ b/src/nifmodel.cpp @@ -1034,10 +1034,17 @@ void NifModel::insertType( NifItem * parent, const NifData & data, int at ) return; NifItem * branch = insertBranch( parent, data, at ); branch->prepareInsert( compound->types.count() ); - const auto & types = compound->types; - for ( const NifData & d : types ) { + for ( const NifData & d : compound->types ) { insertType( branch, d ); } + } else if ( data.isMixin() ) { + NifBlockPtr compound = compounds.value( data.type() ); + if ( !compound ) + return; + parent->prepareInsert( compound->types.count() ); + for ( const NifData & d : compound->types ) { + insertType( parent, d ); + } } else if ( data.isTemplated() ) { QLatin1String tmpl( "TEMPLATE" ); QString tmp = parent->temp(); diff --git a/src/nifxml.cpp b/src/nifxml.cpp index ec48389b0..99fc41fbb 100644 --- a/src/nifxml.cpp +++ b/src/nifxml.cpp @@ -276,12 +276,40 @@ class NifXmlHandler final : public QXmlDefaultHandler QString ver2 = list.value( "ver2" ); QString abs = list.value( "abstract" ); QString bin = list.value( "binary" ); + QString vercond = list.value( "vercond" ); + QString userver = list.value( "userver" ); + QString userver2 = list.value( "userver2" ); bool isTemplated = (type == "TEMPLATE" || tmpl == "TEMPLATE"); bool isCompound = NifModel::compounds.contains( type ); bool isArray = !arr1.isEmpty(); bool isMultiArray = !arr2.isEmpty(); + // Override some compounds as mixins (compounds without nesting) + // This flattens the hierarchy as if the mixin's rows belong to the mixin's parent + // This only takes place on a per-row basis and reverts to nesting when in an array. + bool isMixin = false; + if ( isCompound ) { + static const QVector mixinTypes { + "HavokColFilter", + "HavokMaterial", + "RagdollDescriptor", + "LimitedHingeDescriptor", + "HingeDescriptor", + "BallAndSocketDescriptor", + "PrismaticDescriptor", + "SubConstraint" + }; + + isMixin = mixinTypes.contains( type ); + // The must not use any attributes other than name/type + isMixin = isMixin && !isTemplated && !isArray; + isMixin = isMixin && cond.isEmpty() && ver1.isEmpty() && ver2.isEmpty() + && vercond.isEmpty() && userver.isEmpty() && userver2.isEmpty(); + + isCompound = !isMixin; + } + // now allocate data = NifData( list.value( "name" ), @@ -303,6 +331,7 @@ class NifXmlHandler final : public QXmlDefaultHandler data.setIsCompound( isCompound ); data.setIsArray( isArray ); data.setIsMultiArray( isMultiArray ); + data.setIsMixin( isMixin ); QString defval = list.value( "default" ); @@ -317,9 +346,6 @@ class NifXmlHandler final : public QXmlDefaultHandler } } - QString vercond = list.value( "vercond" ); - QString userver = list.value( "userver" ); - if ( !userver.isEmpty() ) { if ( !vercond.isEmpty() ) vercond += " && "; @@ -327,8 +353,6 @@ class NifXmlHandler final : public QXmlDefaultHandler vercond += QString( "(User Version == %1)" ).arg( userver ); } - QString userver2 = list.value( "userver2" ); - if ( !userver2.isEmpty() ) { if ( !vercond.isEmpty() ) vercond += " && "; From f64b28dd08f17572a93e988ebcfed50afe454392 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Fri, 26 May 2017 12:15:35 -0400 Subject: [PATCH 023/152] [NifModel] evalVersion/evalCondition cleanup I was not assuring both conditions were always set to a valid value and sometimes one could be left in its invalid state. --- src/basemodel.cpp | 29 +++++++++++++++++------------ src/nifmodel.cpp | 31 ++++++++++++++++++++----------- 2 files changed, 37 insertions(+), 23 deletions(-) diff --git a/src/basemodel.cpp b/src/basemodel.cpp index a20435b3d..0259ab828 100644 --- a/src/basemodel.cpp +++ b/src/basemodel.cpp @@ -749,28 +749,33 @@ int BaseModel::evaluateInt( NifItem * item, const Expression & expr ) const bool BaseModel::evalCondition( NifItem * item, bool chkParents ) const { + if ( !evalVersion( item, chkParents ) ) { + // Version is global and cond is not so set false and abort + item->setCondition( false ); + return false; + } + if ( item->isConditionValid() ) return item->condition(); - if ( !evalVersion( item, chkParents ) ) - return false; - - if ( item == root ) + item->setCondition( item == root ); + if ( item->condition() ) return true; - if ( chkParents && item->parent() ) - if ( !evalCondition( item->parent(), true ) ) + if ( chkParents && item->parent() ) { + // Set false if parent is false and reject early + item->setCondition( evalCondition( item->parent(), true ) ); + if ( !item->condition() ) return false; + } - - - QString cond = item->cond(); - - if ( cond.isEmpty() ) + // Early reject for cond + item->setCondition( item->cond().isEmpty() ); + if ( item->condition() ) return true; + // If there is a cond, evaluate it BaseModelEval functor( this, item ); - item->setCondition( item->condexpr().evaluateBool( functor ) ); return item->condition(); diff --git a/src/nifmodel.cpp b/src/nifmodel.cpp index 5b11d7c49..0658fe7ec 100644 --- a/src/nifmodel.cpp +++ b/src/nifmodel.cpp @@ -152,29 +152,32 @@ bool NifModel::evalVersion( NifItem * item, bool chkParents ) const if ( item->isVercondValid() ) return item->versionCondition(); - if ( item == root ) + item->setVersionCondition( item == root ); + if ( item->versionCondition() ) return true; if ( chkParents && item->parent() ) { - if ( !evalVersion( item->parent(), true ) ) + // Set false if parent is false and early reject + item->setVersionCondition( evalVersion( item->parent(), true ) ); + if ( !item->versionCondition() ) return false; } - - if ( !item->evalVersion( version ) ) + // Early reject for ver1/ver2 + item->setVersionCondition( item->evalVersion( version ) ); + if ( !item->versionCondition() ) return false; - QString vercond = item->vercond(); - - if ( vercond.isEmpty() ) + // Early reject for vercond + item->setVersionCondition( item->vercond().isEmpty() ); + if ( item->versionCondition() ) return true; + // If there is a vercond, evaluate it NifModelEval functor( this, getHeaderItem() ); - bool expr = item->verexpr().evaluateBool( functor ); - - item->setVersionCondition( expr ); + item->setVersionCondition( item->verexpr().evaluateBool( functor ) ); - return expr; + return item->versionCondition(); } void NifModel::clear() @@ -2353,6 +2356,12 @@ NifItem * NifModel::insertBranch( NifItem * parentItem, const NifData & data, in bool NifModel::evalCondition( NifItem * item, bool chkParents ) const { + if ( !evalVersion( item, chkParents ) ) { + // Version is global and cond is not so set false and abort + item->setCondition( false ); + return false; + } + if ( item->isConditionValid() ) return item->condition(); From 375be20255dd51bb9e2de33fa7213c607826e3ec Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Fri, 9 Jun 2017 07:04:20 -0400 Subject: [PATCH 024/152] [NifModel] Fix mixin names for nif.xml changes --- src/nifxml.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/nifxml.cpp b/src/nifxml.cpp index 99fc41fbb..30901ead0 100644 --- a/src/nifxml.cpp +++ b/src/nifxml.cpp @@ -298,7 +298,8 @@ class NifXmlHandler final : public QXmlDefaultHandler "HingeDescriptor", "BallAndSocketDescriptor", "PrismaticDescriptor", - "SubConstraint" + "MalleableDescriptor", + "ConstraintData" }; isMixin = mixinTypes.contains( type ); From 721c8b8ad7bb621195978f2ffb6c42e88e394c03 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Thu, 18 May 2017 00:09:28 -0400 Subject: [PATCH 025/152] Remove some unused icons --- res/icon/arrow_left.png | Bin 2845 -> 0 bytes res/icon/arrow_right.png | Bin 2853 -> 0 bytes res/icon/button_loop.png | Bin 552 -> 0 bytes res/icon/button_switch.png | Bin 512 -> 0 bytes res/nifskope.qrc | 4 ---- 5 files changed, 4 deletions(-) delete mode 100644 res/icon/arrow_left.png delete mode 100644 res/icon/arrow_right.png delete mode 100644 res/icon/button_loop.png delete mode 100644 res/icon/button_switch.png diff --git a/res/icon/arrow_left.png b/res/icon/arrow_left.png deleted file mode 100644 index 0a19b1508ceb0920cf1d8a27a6815abe15f04981..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2845 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 Dd(1)9 diff --git a/res/icon/button_loop.png b/res/icon/button_loop.png deleted file mode 100644 index b6aaaa56553b12cda0332828c90066f62e9ed99a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 552 zcmV+@0@wYCP)p8g+&+UfV27!Q_X!_G(KetA>n>#g!e3UGh@5qa*hgedWANHp@7;VI%U#(OI#%R(oV~j-!03WJUt1s~D8++D;c?nvFIse!&^slX2p$>tEe0R1oBRqHQr1B9WTlHw!uwv249MO(^tdNB=FZi@`cCM}>GrMD9jDb_0GdS_F=`$LQDrLB?D}ED?$K#4_I2;N#NYfO0 zy&fU~zW)T1$prjo{{{@)1G4~^m_OE)El?ubL=Z(ANE5_SMDe!U?Oyl$eMr)r%OmR$ zmn~?mA^ZrL&29*};^InEVp$_lhjAW#7)r69pX5Cm*guQwXaR`Zd} z6myv_K;4l+3@VUp5Mx402|CVV%V}Mp*i+2KAeF@T$DnLMzDqDdFjg`XkO5VM*eVv0 zfjDS^4Px321w}O^q6)}pI-AZA54eKdoOSVmL4y5K`FL%79)#%(7B7p>`Fwu0-|x3d z2zLcE^@A-;~62M-EJGaG9HlKZU?q)3ydxeAFFVU zRkJYnWokC>$-3ooDcHbqoIj27dcDpJ{`GJC7GMBrgInXQI2qdj0000skel.dat - icon/arrow_left.png - icon/arrow_right.png icon/img_update.png icon/img_link.png icon/img_flag.png @@ -17,8 +15,6 @@ icon/sizegrip.png - icon/button_loop.png - icon/button_switch.png icon/button_view_walk.png icon/cube-top-16.png icon/cube-front-16.png From 3b852edb725473de88398f1c4c28eb6f5378c0eb Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Thu, 18 May 2017 05:07:23 -0400 Subject: [PATCH 026/152] Remove more old icons --- res/icon/button_view_flip.png | Bin 434 -> 0 bytes res/icon/button_view_front.png | Bin 607 -> 0 bytes res/icon/button_view_pers.png | Bin 565 -> 0 bytes res/icon/button_view_side.png | Bin 597 -> 0 bytes res/icon/button_view_top.png | Bin 598 -> 0 bytes res/icon/button_view_user.png | Bin 589 -> 0 bytes 6 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 res/icon/button_view_flip.png delete mode 100644 res/icon/button_view_front.png delete mode 100644 res/icon/button_view_pers.png delete mode 100644 res/icon/button_view_side.png delete mode 100644 res/icon/button_view_top.png delete mode 100644 res/icon/button_view_user.png diff --git a/res/icon/button_view_flip.png b/res/icon/button_view_flip.png deleted file mode 100644 index ad081f63fc15496553950c134abbe8f4a686be92..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 434 zcmV;j0ZsmiP)wI;0*jaTC4kawx2^lA6w{8CAm|5Y3HEP$w*v7Ca(cy6F35m;AOY-;>?aXQCllU zSsMlg>x`IUa+Lj3)A+zFsqZ_m63kZIbg-V2>EUwPmnE2XU4P~Cxw;EkE|{qz;+h;l ztGpU-5^_3w@C!ZGs!o3p~Qt$ESu6PN#Rc!u^Q+y@|);=z6(WbAtc= cH+~B+06Gu+{*&RJ^8f$<07*qoM6N<$f`C)GWB>pF diff --git a/res/icon/button_view_front.png b/res/icon/button_view_front.png deleted file mode 100644 index 2a638d4e525399165209732865d1543bc08e1f60..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 607 zcmV-l0-*hgP)WdKxYbs#q&N_B1^GB7YVATlsIH846gG9WE5I503Wh^+ts000McNliru z*9Hg#7BRzAMo9nw010qNS#tmY3labT3lag+-G2N4000DMK}|sb0I`n?{9y$E00Fm2 zL_t(|+GAiC1&mn4B-!NnqF5DqP1vRR)tT8@xc>b6`$>XFV`!o#zelJd-1o9jt(=(Kt2Xm zhC2;=|7*1+GKgIH&tT#bUY9Xh_vJz$I|UtE+3-e2O6c2-hn-Gd+!Wvxb;G z{E%U0ePm>qf}xO)zDq;(`twJrRI0zfz4Z}u0luu{;ivk30Uc1a&cB9ZRa z_E<{hyZkbj_gm11>=UjH^^Zs5@B!CZId zF9n*WDMot?+2kcT1#piGAq#||5vDqI6lRQ+@hQLnAd;ek?GjG~00000NkvXXu0mjf DGpzl; diff --git a/res/icon/button_view_side.png b/res/icon/button_view_side.png deleted file mode 100644 index 12eb07b41c26816b71eba8508883e6ea05f71895..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 597 zcmV-b0;>IqP)WdKxYbs#q&N_B1^GB7YVATlsIH8nakG$1W7I504K+f(2G000McNliru z*9Hg#6(VfMR8#-}010qNS#tmY3labT3lag+-G2N4000DMK}|sb0I`n?{9y$E00FH@ zL_t(|+U-(5NE=}sec$)pAI+U#E(dZASgl8CP{C4SgwmD_u?X71Ne9O)okY5I>FD6> zBxILRsJ2_Biy}cSq~M?iEI1?wL33C%Cih+MdW(=Do$JsCKX`9>{C@BKfd3#Jyl+ZX z7hdW5bNBlCiR}BDN2yj5;_M7KItD)80*BoU^jTmjYnsc|M1qNQ+B_|nv0AO-+59}h zFhq5G8*8;%;H&EQ&360UPhjuPfG!YHc%dlo$8$L&Uo4s{>+6^t9!APAKpY3OUdMx; z9=NF#?aSvU-%|Q@Tv6WKZO(xV$m`ce*pmbSGuVt5{PBq?~APBUm4J~WdKxYbs#q&N_B1^GB7YVATlsIH8eUgG9WE5I5053DFc82000McNliru z*9Hg#6(ohE@nHY}010qNS#tmY3labT3lag+-G2N4000DMK}|sb0I`n?{9y$E00FK^ zL_t(|+GAiC1&l~+UUo5;a2_dJ%P*hbtNr=M%FXou7x(XP&+amOzk8YC^Yt|h4Bt;< zGk}Ld$U9F+-oD`Ln@bE98b3vzJ%7jeRIrO7%*T-7|Ns9CyN^9(cy@8hKX#TsXMR52 zP{i=#-cgtVOpFrYbJ;|#E8l(N7Ff_1F1lvfG)8V=RR(3@2Mo+?j0~azObmPi5)5AE ztW3u8kIk55Gj_2F==s20&m_itW4hSaY0HGZ%-NdN{r=CJcb^$}xH%c*g#R!Ia4<0Z z`3qF^;|2p8^DlUoU=Je~e3$ zTS%GV-HU5XhNe!8-0Tbt?;dRb#lrlDCB)B~L0L(Y;q7-G_V+*d)S18E*nk{}3`$B$ z{D1!ZasB=GpEE1Bk`|AEq&N#JJM)ikpZ@;-`Tgy$AMbw)N$CqHshKD+a|rPqU!Uj2 zh$UuNBqb#U*x1-)foySLAaXM@GX4j$zX935ftU-(mt$sSGx_)D_hD=X;EL-1NPHG? kadAPQ*Z&Q4IvflD06i*`5fA+ScmMzZ07*qoM6N<$fWdKxYbs#q&N_B1^GB7YVATlsIH8wgjFd!{3I504x%*A&A000McNliru z*9Hg#6(=06pxXcd010qNS#tmY3labT3lag+-G2N4000DMK}|sb0I`n?{9y$E00E^* zL_t(|+U-$GO9EjO{XTUXe5Ya-jV`95RfZA`)JC_q5A5Mi>_6nXenE@+2`+5oq97_I zav_n4wJ@n1$I>)>llxv70iPOdA%9b;)ct%@YKn z5d@)Sv6$Irvzbn%Qqg|D|1ip6f?=5b!C*!QpVASS*GN1_P24JaszAX0suZB+*Nufb#kL zbGcl;6Gc(d>-8%tx7)qG9SHafFKk~H^E@wmN*Cv6*uSwUrEhN#P0!%na2O@x@kFE1 z$Y6}4z{&M`JtYo>c1~%U_CUM}$8oP;Jm1l()nkyrEJ@N>y Date: Thu, 18 May 2017 20:35:13 -0400 Subject: [PATCH 027/152] Increased icons 200% for high DPI --- res/icon/axes.png | Bin 1386 -> 2041 bytes res/icon/bulb-off.png | Bin 1595 -> 1921 bytes res/icon/bulb.png | Bin 1634 -> 2725 bytes res/icon/button_view_walk.png | Bin 599 -> 1483 bytes res/icon/cloud.png | Bin 1364 -> 1807 bytes res/icon/collision-ball.png | Bin 1463 -> 2425 bytes res/icon/constraint.png | Bin 1723 -> 2756 bytes res/icon/cube-front-16.png | Bin 1097 -> 1619 bytes res/icon/cube-side-16.png | Bin 1234 -> 1657 bytes res/icon/cube-top-16.png | Bin 1102 -> 1645 bytes res/icon/cubemap.png | Bin 1857 -> 3395 bytes res/icon/flip.png | Bin 1337 -> 2012 bytes res/icon/glow.png | Bin 1560 -> 2558 bytes res/icon/hidden-disabled.png | Bin 1518 -> 1986 bytes res/icon/hidden.png | Bin 1297 -> 1631 bytes res/icon/light-rot-horizontal.png | Bin 1345 -> 1991 bytes res/icon/light-rot-vertical.png | Bin 1353 -> 2019 bytes res/icon/load-view2.png | Bin 1190 -> 1519 bytes res/icon/load.png | Bin 1417 -> 1774 bytes res/icon/marker.png | Bin 1358 -> 1949 bytes res/icon/node.png | Bin 1501 -> 2351 bytes res/icon/normals.png | Bin 1987 -> 3958 bytes res/icon/orthographic.png | Bin 1781 -> 2287 bytes res/icon/pause.png | Bin 1116 -> 1228 bytes res/icon/perspective.png | Bin 1738 -> 2407 bytes res/icon/play.png | Bin 1410 -> 1404 bytes res/icon/redo.png | Bin 1213 -> 1452 bytes res/icon/repeat-all.png | Bin 1679 -> 2615 bytes res/icon/repeat-single.png | Bin 1301 -> 1522 bytes res/icon/save-view2.png | Bin 1461 -> 2020 bytes res/icon/save.png | Bin 1003 -> 1117 bytes res/icon/screenshot.png | Bin 1295 -> 1495 bytes res/icon/select-object.png | Bin 1823 -> 3151 bytes res/icon/select-verts.png | Bin 1386 -> 2036 bytes res/icon/silhouette.png | Bin 1152 -> 1465 bytes res/icon/skinned.png | Bin 1728 -> 2818 bytes res/icon/specular.png | Bin 1597 -> 2500 bytes res/icon/sun.png | Bin 1417 -> 1880 bytes res/icon/textures.png | Bin 1213 -> 1244 bytes res/icon/undo.png | Bin 1204 -> 1459 bytes res/icon/vertex-colors.png | Bin 1699 -> 2238 bytes res/icon/view-active.png | Bin 1427 -> 2222 bytes 42 files changed, 0 insertions(+), 0 deletions(-) diff --git a/res/icon/axes.png b/res/icon/axes.png index e7c442567176b9c1c70db6ad2633ee4901e0e06c..87bed73d49365427886e8557e5b2d9da6c6407a6 100644 GIT binary patch delta 1308 zcmV+%1>^ea3i%HqiBL{Q4GJ0x0000DNk~Le0000a0000a2nGNE0O0_bn2{kU0cnv+ zjwLoNV=*&0AU8QKF*P|jGBhk8GB7bVFEBYTF*GeOF*-0gIxsP@{apbdIWa;wL^3!u zGeSZ)F)>9qK{PZoLq;|>GetHyGcrOllX?OiAUQEYI7BiyGc!U$H!(3qH$gNsGebr; zHZw&wI5RRrGLyCfBmqN{+yXizF)?OiF*IT{Ej2eYI4xo~G%+nWWHT`>F*jj1H8o=~ zW;QoBlQsi>f7=gH^Z)<^CP_p=RCwCtmrH0|RT#&=nFMi@_@r)ZkhF_}8w=7D+hD3I zDN3y*r~wx)Di#+aqEHtVDlXgt=q0g77<$(;y#M09 zzVyHPuXX>`rAX^6snKtx#CvsWo}&(y!g?Hf`qywsTAua|Q#COQfi3Qwd*n8G(;SjY zozND?Ow@C4lG+z_h#|`~o#4mSOcllC>9mq?aWH=D_Mqzy#c(GEOoTKJ>tYIR z;qF+Ffm=G$| zWdfMcBZ@>H1x)}!o4A?t1P0e){*tU=r~f5N3z_V=ewP5X7P)OC=$WG5HujQ}Of z9ipw_kOGj|GC=a0wgiyM!)0_wfqK^u)LIV3M~@O)EbA%u8O%(M`;nJRWG{S>Y250swvzhP zpHFU&Q~!dp(T?Tq7Kxb+mTf0l098JSf6nWAnd0mLauAVkemTt7Jw&gx?9l1CrvT$! zfObU2B^)uy(y8}{st0b=|EpC{0JwtJ%7HM4QH}larIXB4El>N2xlzElQ-B!EU?Ss? z6kOIZuldCh=0HsWA{LGI3S2x6cg{>Oo0gOMOnEE|)08mP!6HU0{qFBV?C$Fly#usSq8aQf1RfJt(gFs)Ifd`etf zPJABuD!UdMx9N8xh{vo z)EPD`2{3m6<{P+EL>tAvrr2pXe{*(x^I)zAGS!Ebjqw2}2tdVHKahEN@$0(!Rul2 z>o67|zp0SF(-D`997B{e#iU8Y?~~6ba{*o6By<9{XbFp{k8q! z)$8rEB|k8@%~;#h(~}VO`+@pu`~UysYkz&VKRn%hy@W(v-R3k8GYhuOGZ|+GPf+<8 z!DO^pvgz}G;~VWR%|Bk8Yklo_ohP-}u>OoCe^SB2OB~-ex3sV@b98riEAM_LefYb+ zfE0hiAUt@KN?l7^QhU~+{nRD_U&~2 z^qH2t_q^<5j{lg_`TBfnz?Fr~;`(t3S>NV-bbopA>~)^^mhyj6fBgNw{>$^*JXsDO xmmj&oQ?{+{R{7HI%@*$;26voIYhYkz_&Dv?>rAfgX}~1J;OXk;vd$@?2>|hy5cdE8 diff --git a/res/icon/bulb-off.png b/res/icon/bulb-off.png index 08725194a7ae6336cf23fe1f5a254f7075d02df3..1947671c873d7be6d4ff6a83802ba29b378629e5 100644 GIT binary patch delta 1166 zcmV;91abSj41o_JiBL{Q4GJ0x0000DNk~Le0000a0000a2nGNE0O0_bn2{m3B{nT% zF*7(IH#sgbH90skG%O%8FfleSFgY(VG%YYOIxsmpFfpPL_zzfQFn*h zd(N$=TCJ+1)V%75HQ!+-O-)UI1zKBM2Yo)@HQ=n*>pjWwN~Q8`dwcuC*49>HV`Jmh z=;&ziplf>$;4b5pIaum1oZmP)0k@p!yi zcS4hslm7Ph_7{Ob;3h~Zf`pozn-wgiY8Fzkju2ACVo`z6Og^8#H8wVXR;n8zSjKA@ z#v33CQVOI@LU5ac%XV0(TrMlP%Oq4N6iiYel*r|B*I}tjU1A8zV<2IY0V%~NTxJrY zg>*u+5G_Zd9*;-HqaYeX_mN`@A%Kr}baW&`p^yQnU@-W{LL_BpAzBBn66>gJHmfq3 zOdbd$(0@9z5Ug+;iVY!uKu8R+Zx=&i8NQPsV+w9GK=jUjR@(O*;?|~8x`8gzA!9;R zAJb(d__5Yi$1wzVM8qw83WTH{Lsm3D){jm(+m;@8eLr zfOF-HoGskOvb*$?S_nu!t6>rE_v%)l4G#|&kqQQY_e=?*$f9)D7-fM);0o8MYsEM; zG?c=^=VAJnwX#Bg%8MP)zfl(bbC~&`zVPRorX=!PYSzk&JtIJ4;Nwv4P_jP}N z{|#45*wxk5OZ@dbc_n$;5u(h>%F2N2HFY|j4zZjN0^CVDA*mKokkNXE>x5{5<>h5_ zheJ%ywYBSna3#v} zI>fsf73x-CpT$1EQ3kSk*MqvNtLq&Cd2nrQO$hF?5O)}Z9kR2H*kup;`uZ;7d91Uu zQ%z4#o9UxTc4bnrSj;2?nKWfx{r5vlu%~$CN3OaYjYfk!nI*mwi9`;Td5hOL66?o? g5c;qFLHi}Z00)=?*gHWJ`2YX_07*qoM6N<$f(LH{{r~^~ delta 805 zcmZqV-_4`g8Q|y6%O%Cdz`(%k>ERLtq=i73gAGWAo$CvqsJLC#R5#hc$WX!DQqR!T z#M01EN5ROz&{*HlK;Otx*U-?)#N5izY~$Y~MtKu+QzHXQClgmkV?#q%6DMaEa~C&P zBUcw!M;A9o*U5EEvQRaSIMwW8ik~dUq&C@}xuO1mbMt2g2Brj07srr_TT_Bhue%f= zV!Jj}Ax@6%iRw9y6B8C1A9m?w{=@dje8vy%@BBTS#d9o-3SH)`5M~n2Pm-9Z;mD#B zprMptr=RklP5W&7?_8s*u$Ph2B~3mL#_~q5 z{rBrLBd;wkvGQfvBy+s*NZMw>lPRB)S6{Vif62aqVcQcmq1d$<1`=C3Rpy`Xc3P;= zb#xKS%M@k#q#c-(m3i96M6(X40(tIFIlOZ^TlkaDw)GKHy`E~PeW)^;xyOL#v012-Q_AbFx7J;Moi9*)Fx$Q$ zZ-urCpLO`vtgn}A?C$G}aSJByjN!VG^TY15VU(Ba!a^fuX@kt$?S~aje1mhgM@I$< zuv`&4`uE8U+vhBT6CYSs#%Rsm+jcm5!Coi5>5~~l0?lSG_5QcCM#}CvSLxq+LCL)* zkL=xX_+dcL@np_RC)w`jud}%|L&SNnVqNolGDAZ{F+@U>egYgIH!?#)Lq$e4GeSW(F)>9qK{r80MKVM(F)~9# zLoq}`lez+5BsexPFfcSZWi4blV`VL7F*GqPVq!2eEnzodH#RspVKFo@Ig>pDf`18! z+i?H@2Fgi9K~#9!l$cA86Gsq$Yo7Mn>&LP$SO{yU%g2C+i{hYo zC_YMvQbDPr)LE8M8Z2~Q6jRDUgo*+99B zHrC75#Tr_G+Zj9&Q&Uj4R*UfBQ_Ke&R7KB{@h?%>dpOpMVarbmVm8xI2b=jmPO zA8o?N4}L~(nHRU8W}sCl%T3t-^&2$420l8if{*A3$Tb_kIG$F$pgv5l|qLdY*?`3aInso`MMwB*Zid@f;wKx2%C&1}YfH zt?5vjT&J;}CttwT-J!MOMl2@TokbyAnk02P+=PSuZ55`lrD4Jq4Sz(r<06oUW=b?u z;D;6nT?7=)c)KIl_369-D>~x+`#X0#gy5nE?XJOWusP;!zDeyd)?u)BqF` zbtYMwnsG!fl`+2$^;jPzKC26_m@?yu`ZT?;z}yL79tUb1g~8!7-Miwfo?Fk%7@Av^ z1r(}XQvg>isLA}UDBmC|XgMk8u3xB2r?jfQxpii*T{#DjbAQ;J7vyv1n3>#<2FYPhs z2_>!p9qa2N*YA{0q7$3OmUt#x(=I=@z)ItxQZzRMlW@U`V14@)zA7QdTN0`>YJx;h zND~=Ib8+Sc6dhnar{D7RonYVgv^A*|j!hCI?eiiED!L<}HBc_fCZ z{!m|&4YH+yBCBoW^MGbUrOUhC&)oR@DFki;Io@XYI3MbXl#DFl=SqikaSV+m8+vbK zTE|F)E|WlDc}VW|`pO9HYqs~EfsZj6_o$O=$w>O*zJITunS0WuG2458@ZPA)Hl>%O zhbB9{dHG|2sP{Cn2OYreuWQiRzYfFhvaT`QE@Y)vTaT`m=E@mt-TNEgckt~Sw$&Gn zHL*N2v&syV3|XJQij1A0;X}Cgtph3A9iyZyNmq-xH}6Q7#?V-8LwYE-l3H>2*0PsC zBLVepx__~ctB}0Y1y?Qm5pOj>H_`$D>t=gzL;&>e4%~2ngo15(_AYw{H{Z^@fmPMNZQQJT6JpTaS;2xSe*P)kRYhIGe3x}3=K`WF0O zAlplF$5l|SCy9$t8T9)phAWxz5QVeRsDESrm>sdoo7Ac~bG`E+AV)x%XL+sG7YLzgL` zYkEfSNQZQ-=2@Hvqf43o2kjz<{nO>VV_|3(9X?$AxbP$wIsOx103`qN9HW}8H~;_u M07*qoM6N<$f;ZKY`Tzg` delta 846 zcmV-U1F`(272*sbiBL{Q4GJ0x0000DNk~Le0000I0000I2nGNE09MY9SCJvNB{eN$ zFfuV9H#sgbH8eRfF)Sc5FflVPF)%MOIV~|UIy5&rF*dRNVF4gUIW;peH#I~wML{w# zF-0^vHZwv*G%-arFfc(dLpd^&egYgIMmaSzF*h|sG(|x&F)>9nIW{vwL^Lr)H83zi zFhe;qlez+5lO6&llRpE1f2gyeEC2ukhe=uptpiOhju%O|Yk>_Ozkp{QAzE?_m8~ zILVVU^ZmT@&G}|hQS>qez#CX?@BW?>(*OE0*}ig=KaVnvpS%#ua2f=2xjoK6kd3b&AgJQg&xf$8!Q z7T2HBazGlSrK{Bu15?ya7euJfAHYJK;~Hb`qqdG$H%e?F=nozZ^RfK zDAo2Xo7Hxcva35{+pXcm~_oi4b z=S)DS(jb|FY;K2 zNyo;860zinVK!TG8x0#_7(bC_)5gX|$(3g@lGuxTWc_g=rQCA4+A0>S-R3~_CiR%e*9&qqoeBGFjS9sc6OGuVQXvaC{U^JNv(FWoBWsY@h_qO9zO&a Y0C*-ByKc^D5&!@I07*qoM6N<$g6u|kCIA2c diff --git a/res/icon/button_view_walk.png b/res/icon/button_view_walk.png index 2c36474a3fa171fb35429fc345a6c46bf200fcd7..9593a134bbd5b8441283cc0235ef66a304a2f0f4 100644 GIT binary patch literal 1483 zcmaJ>ZA=q)9KN<-3*)7{D9QwnQxJsqdIef~P-yKHTBU%{04~c$=^gYU*E_C<9Bk7f z1nM*hlOUfAY&!R0)0hz#rWtN)6cfXmWibX_2&fEfuWXC(odJf1gk=Y3uH~xe2Cmh?5`Yn>>j}_GIU0#d z!hyFn-ysSBK$1qKRIRN7G|?;p<|;J`Cz729YIRB^SC^;G$pY0XL<6gIFp{HCA*f1+st|DPfrMx* z-hftO#cQ#IlOA$$9D~AeYip~rHCsuujWD9q>0p%_R;v{PLg8t1a}HkN_Uv9)zzC0% zB^i#S-C$7BQBSvUdPvChbqN%+A?x<6wMl3g%sUtuQL2I^tpjbg|A$i42HL|_5^wVT zPhpR}jUnJl!b7*PPGN8jyMv(^)Wi}Fj%MvN-L&4t3Kz}M9v97kCR4C$S}?tabmMfZ zCu5akv!PbEhjX}{gcZ|6f{KzPadbaYtTAeHk!+J%hahHcUbYr9o3)rKw^)rK7_!dA zXlDyWxVd#M{)VgGlpC}I#R!oxf+dd~1g~NvV3Af;QRK#P}xs%^lonwv4OfMYo|#Z~3ne_li5dGVBcZPiKUUz<77TuF)Hx7-E0lGFy1yvu7Gn zTOU1L7jq?~J~V4~A>c}TV3f)x=&tIF`30r4eDdxkzi}9SRv0yQ(D3dF^TPyR+u~5z zfs&n%)1D?6z7+Y71+e94-?Vs%On=p5KBE_98+%($-0RNm<$7DUra`3&= z7i)o~1fV%Tm6UCZ6$Qd8<@=6?zH`XfAaDYbCOvM*!w3zNIGURJ%U9o0LV09l79Q5=*+{x4knP~QktYMy!?zIPiF6%{2n@A4*ua9?A|{7B>D@Br33vm`Td2` qXz52i*U}EUkY!~4R?mDmAdUd47m4SW6EfR_zgMfd47WdKxYbs#q&N_B1^GB7YVATlsIFgQ9iFd!{3I502^Jx~Dv000McNliru z*9Hg#7!(%MzkmP$010qNS#tmY3labT3lag+-G2N4000DMK}|sb0I`n?{9y$E00FN_ zL_t(|+Le;aO2bePhI2D%t)-=@5F|-K(1I1R6cllzkKn?`5PXHM-1!88Aa2D~cZ$%B z6mb!y)kSG)=)!DTNSaF%|5I9tr57C-PUd9JcV_-MEETg%CUaOU79WEkcy}D9LH)s) zc#upcPvC2?>uffw{Tq--B(`aqrc$ZY3apGR?q2|gVFUso4%nWeD24IATrRhR7PE5! zVHlc#5D*0&LV$(`BuP3hl}c4n6t562%>;O!R~-xn1VmwNW6LDcW8;#Uz&THl7#d lPc>Cl*YN`P$yg`LP0Vy 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 DT!kXc delta 732 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 Uxnr^o8~^|S07*qoM6N<$g3EfBoB#j- diff --git a/res/icon/cube-front-16.png b/res/icon/cube-front-16.png index e7175505b591063c960a17119b35bb7b02cb872f..e0b21aec1eacd40b5f9a00f8018527dfc628f40b 100644 GIT binary patch delta 1042 zcmX@fahXT4Gr-TCmrII^fq{Y7)59eQNUMM_2OE%l$S`BZL`4n8w25AQs%E;$hQ<~O z=9YSfrj`~)COQg628L$(2A2AUCb|ZORtA<<28I*=mQ0?-m@+wwQM10l*VoFwC^J1X zFEPg@Ke;qFHLt|e#a5{zw?Ho?GsVinz{SPX*~Q4rz`)ei#L&>v)z!$&*~rz@$8b(tg;EFkKP zaH*U8i_viMVkZ3im2nfv)1#RuQdK)XR|-Q;{HTK z9_DF>v$mc}EN0>7y;1alsa}G~Y`uWn^2;lI)Gp`p98O7MywDP|`fB{ws=wd4I*vOu z9825$HhXK7>8tNmd*7S$zWn}s`=gIPE|*x@W^RpQ-X|Y@NJfyQ@czq^d!|yoJrSFm z3^qobndhU%&hfawLXBHNZgJRZ`5iIqsteNE()_L21FAiEM)7{9~>f~=U^GwUK%!G{*=a?H0O#ittfKT@I z*I&okniJ!f25mih=u4IDo#&rzb*7)*%$j62`#($b!Ti(7Cmn=*cI5L+Xzp%}mn`!1 zZ+N-YHF*PbhQZ-EH5Qe!eC_+SM7mza@YT=0DdQyI!?5|~jk$$s$-2p6^POd$d^q~> z=cl9RHZ*_Yp1@*xr{(3BHzoXAUmI|r{uj^5snV!2fn}0|kO!0Jg8#BD>(^>9Zr)V9 zr>vm*i-*dmpa-v>Z#8Z)c$*o$Z*Anv`u!V>oK60eER(e~OMP7_wOmq2-Tj8zzu2a= zRzEJu)L+e98S+ZfHSmwzZ}okqJq{N;3~uIpTLi?3*ZC{<9Ly8=Y%~4z#a_3?$CCr5 z#rAE}<>6{M`TTRq+A!z!3Hy|N76wSfJ-xv3#(`%uFOSsf2%RRcrI%XMbu0EfHq_y` z#~?nn$7*re^HVQvHyUKD30iqY?9c{Y0eXRkj5%;pT9u6{1-oD!MRxosP zG%zwUb~P|HGc+`GHF0usGk0_|ax`*sGIw?{cbRO*Bm-0AhEvfqCW9ELNzS+wDJc|} z1f`}~rQ|1<=BDPAc)HjsRpb`vrDUd9LG`-e(mT0`*{S}LVon>--+Mh>978H@CH?vT z-=0~-VtxGnI)3KP2Ei6(m$goO7A zw;9hKJb17>LA>Hl$`Ailo3Ed)-GBPv!NUBHkB+X>QDBTSZEH91d?r7Ksorv5{eh)F zKkKqpPDqoO-`VICWsr3s!JvV4wt|N4gKMAWK0G_y{M3BA+Mq*O4EtPISpO)T{r>)b z`i}DV^DMb7&#`6N6+P*wtgZd4DHicbl(#^!&XtDzHlzQW}V)hISOe;NI978H@ zy}9n2aVtQE;e*O)H8~vMb(WGziii}H`RI*HEx|2lGLfMc=A*3$=@3Jhkq|VEa>oI z_r1LRfHKo5u1yOn;?};aU$^;9@*k_7(@&!oX>=Vi;F-$Jq?WQ{npfmk9q)tpf9UWq zOlWjC@TSBn|M0g6ov;Z~m)@3rjL~y{$;TJ-t7@*$t&N&KA#20#bF(#z8CdU#+00}( z(sU-NQO;(lay(P=XF5T(kRg`$?<(FxSa`)Awd+TM3DkV1t zTyc1Dyz!^k62$d3!C#AyZbC`Y_GmnW!LOwKUE%J5xBp( zzHZ_H7du8-q7c2vIJjJT)>s+n!p*7ryJ&d|Gnz8SL^B$E7yif=BWD*lYH#v ze=oI~yRgDaM0$ToC$G_+rBfod3h|A{16g5+0XmiS?@3{{|7Vf z7yMDSH9Io{e`Xx33pm0jwSq1OtxUIso%ft^IZl8MkY@e$B>F!Nq_$T zw`bM>w;n5C<$D_{Hjo3D$vH~0Ey>gxof z^8M!tvTmFyDJf|xZ(p}3)X_0<{=9kjK3-oRFWu4AC3NM?;bZ?^U%$WqOi%DKpP%}w zsz(`0%$P6yJlj$N(QwAr(N8ye_7xccSubAOKJUeQDbkIj=NUD8WT zd^r7wNwbj9>+|>L@BdunSycCL+q6!Rvjz;8c~aUMVmIX8Hd9rY>Zs83j6tC-lkFoT b6A!}<(adD8+BQdE>@aw``njxgN@xNArQWGi diff --git a/res/icon/cube-top-16.png b/res/icon/cube-top-16.png index 6843f24f74f4d073d81fbc8910656f9b41b106a8..30a3606731fc55f8c368f819ae584c99d830e34e 100644 GIT binary patch delta 1068 zcmX@d@s>xiGr-TCmrII^fq{Y7)59eQNUMM_2OE%l$S`BZL`4n8w25AQs%E;$hQ<~O z=9YSfrj`~)COQg628L$(2A2AUCb|ZORtA<<28I*=mQ0?-m@+wwQM10l*VoFwC^J1X zFEPg@Ke;qFHLt|e#a5{zw?Ho?GsVinz{SPX*~Q4rz`)ei#L&>v)z!$&*~rz@$X3j>Y&Ws$A;Tk+^_ZRNJ?`7wWME*L>*?Yc zQgQ3ejoaNqff5WK&S%DChg^7j=)&Q=z##9S>@BY@@4Dj5*_nGGm}O^Vj^LL~{2MPm z^?zY0`iom>OSk@4fg@{rpKY2Hd**^_q{p?`qCE>YU$#6P(VWS@(U|2dyL|eyGY@_i z{&0IHJy(I_g0$ZBY!2SKYsK}t3K5z5DB{)RQh zu8yB}vTnwb=UENg{3~*&DLyFgV_K;$dN3qfHo-WuVXLU%<8w#r8&%HCT>AJr^Y4D2 zIg2!f62CP#D*Eg^=&W{>$z^B^kYz)!3|m^p$0b$UH;i$%$QytUfK8w{Gt2 z`FFE7`#*dX%C)(1QsmQxU%$0h)D_%5cG=v~-=$B`G4lA$-!awNyf@EZ3W@vZemm{K z{3Yvr5_#@d_dakto@x3&-RJbo_1&BQ8b0Eyx0JtM*}^Z>dF1fD?uP|8+K(p~NZjV( zS#`a;V#x~2x(l`}f07qAnoMB33uO&!gPngx*yeCCFrL2yX7E32a-IXr!TW;Lw=4|)8?Zo4D24)7PPpaG3Y$}uo761&Mu6{1-oD!M< D`*6C* delta 498 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 KMNUMnLSTYHr0vB3 diff --git a/res/icon/flip.png b/res/icon/flip.png index 6fa4df48aec3adec7f670089074f907f93795647..63eeb13fa9bc5a452edf66bf9eabf73db4d8949f 100644 GIT binary patch delta 1438 zcmdnVb%$TEGr-TCmrII^fq{Y7)59eQNUMM_2OE%l$S`BZL`4n8w25AQs%E;$hQ<~O z=9YSfrj`~)COQg628L$(2A2AUCb|ZORtA<<28I*=mQ0?-m@+wwQM10l*VoFwC^J1X zFEPg@Ke;qFHLt|e#a5{zw?Ho?GsVi-#n8mW)yUD!$iURq#L&>u)zHb!$kN5d%+0sDJ}_0O|wcN!y}XRn4Rj?r+HQ}FtG4=x;TbZ+&VME+auah zq%B{l_hT!+ijy)&;HsM)+qN|wmD;jv!mCTI@3I!Uc3rsUs_C_I>Dy&XXX|zKcXdaE zH(i+PrV^wmx;pfOsEAlHEKZ(gI10q;MKTlvjA2Fz}H-WQZVG}P8}*ck?@JX^Y8FMo}A zcFdb8w$F~9Id?8?^R}W!j&<9%nMEs2EO@i;xVYvEPK~17cPFh}xpL3et){%$=LN3) z{P}a%uV24xX8O3T>N;?_Ti^?i$gUW@n@^sk9DMd{S(Mh-d!kJ{>({K&DRp*sc1%fG z!sGir^8W;mNzKo{JYZ*MFRF0cy?5_gCBuI#f|;3_S{;{;9X&d+wOGqy!eU}->sPM4C?+Ph>c*D6>({OG`(Cfs zAvHTARB```>C>lkE4V#7ZG5nzgPmXLF!$aCYu5Oj4J(`+Adqz+L}>o;vO3bxo6aG`M8-CMU#ak`wSWHk(&6Sn&7#}nU{$FJMRCK#Nt zU_!3FlVZm_yOgFH)>l(Gca_QQ;%izmuZc<3s@|%qr*yX9HMZu&o_N?igkPa#0BMRzJ>X%^Y`&tQ&Lj$C3*VJ=i15aj}_Zv^xnJXCd>Wc&`Gbjcj%pQ zqJ3(1cJ}M7x8L$zl>4NV#vFX5`$oxwPZq!V(|8SJ*1Y-map_W3_u}H>_rP@5romt7 zbD8VH;!1v=X8i+tMHg>G?oWK9^erkX>Wf%+s%ip1r`ZRQdUoanc^<>X=M;+PPW~!? z=+y1}++5j+m>3gwn^Xbgerfj1&fs~>@y;cE4rM$)*dJ%*=gXVS^!fac<>SpVsn1zU zp0NKqxLrU#Z^GWXo`>(=yfH}<^{eiwS9R2#asJ?SAj&yLWP; zefnpvl$3mmEI49xDApinVerKLe=4lz<{i1cI{ArEOToOyvA>14)HCrgur6umt!TA7 R4J>jQJYD@<);T3K0RVVXUC00c delta 735 zcmcb^zmrR`Gr-TCmrII^fq{Y7)59eQNDF{42OE%-|NK93qM`<)(nPO5Rb$;`14Bav zGc!Fy6H_xYLmdSp14AQy10XWfH8im@HM24@pZK?A@*PH{$ytoqlXo%cPOfH>Rxoll zGBP!CGBq%DGc`1HHE}X@V=18sW?@=_@C4`ShOf^{aQ?d{~h1Txt60tsiHfO}l-DX~*%Kl21PW>^qw_xnR%z z_e*`$E^qvGK4?LJMtzC<;)%`0c^oWEjP6!*-~H1J&=ISC{4rvt-*PR^H)Yl*Uw>Uy zw?E!TY==Bo40gG*xH3%f6K+!<5H^yzAi zUAdy9B1eIUL&FK)o~0>9KY1=6U)=1q^b^yxsYjCvRe}!)T5#r=NxMC)|5y>TBhBJ~ zkHfxYXIP3FBqwSxUFTs`DB6AZ-)iX{b+4KVS(w&Ix*o~uzGm2T@PT?M<7VFW!%Hi8 zCNpn1<@)==CeZ?i$AJ`h_ z7A%^vvO|MGT3h9K(#DRfylbz&PCVgbWO&Ry#f0*WMqM4=|WL5r3Lx)2{9qI6^@*L_|V>n|pEihp;HZ5UdWic&dWiT-~ zH)SzqV`gF^Aait5Wo9~LZ)0_BWo~pyL_H#5WN%_+I%IESX=FNZXm50Hb7*gHI$>it zF*RXhGA%P=VP!2bF=aO`VKZZ9EjTt|I50FeGc`0}WFjv8ewg zZjag#Wi)TVySLBytO=t5|0mvqeI|d;doDP?rN^Wf@0#DcIb#ynW^%z`F)WJ3A*B-0&=B`5 zLnQ$ggfdnEK`TuL&}_0ai0XBsT0IHOH6>An0gWn$)c8rxs8$pVK$Ocw!y_!*mxl#V zv1GM`?_i#`(M+u-g1O>_qa=S2H8HbS$sHs>xL_0zzyQby13ARsjj)XJW`-Hi$x}qf zj>)_4TT%nm_BPS&HKK(DOYlxtdi;<10nCC*c$t>~gWlB)z+lnChgpv-XU-Dce;?7g z^8#vWO2%q~SvEF^R#u3<{f6j|--+fKSjZ-98Epbs3QTq!Qg2eyLKA<`%=kD1VwsyG zdhBtc3l~INSq6e^aZ!j1 zPf{<#x6t0{(?s{OY43k-<^ss)P=Y_9?6hnE!CW&lVkT{$e@1lenvl0`0w74b*OoX0 zw_jcohge<~4@;YAUk)jml|v=t>c@{0J@BCA%&)%?-MC@Ew8h00P^W8M9RIJaNi6Dl zF=eNlKDSSRG91z%I6Y1D(8JbLKVFf=usDLB20AsFHVvwX?lFIV`~z`_`iQL)g(gKH z2!fz!tE&=qWAm8w04>G*LvC{%1M}g3kQy88uXJ zL_hyz$02=88?Q=pF3LG{#2#rp&lY9_0J)}$D?aSFDS2w+Z=%0AP0Y_*Exqz8r;{_{ zVK7mgYRZykvK&F}8{6Xo-M6GbNzJ8tP>#rnggRGF(UY9@#KCLt8 zkU<^cm^6PenG6A#9j1iFwcyvoKn9?w7gV}VMF*2%lGs&urM$rIKmLg5g%^otXJrPh zd(O{glL85NV_h^Ir=$vCN>mjQ3%;MLn1K4j^N01qH`|#_)`9_wh8B)H^(2EE7~OBpB}lPwM7? z7>0kzRM2w_Q<{J?dU|z_)UI}-sG?hUIdm~TN@r=x)lgg^`X*~T^^cl1a)MwCAa$-! zpY3IxFS0HhW_jnA_KkMa*M8Dz$EEwx`8)e i2LN=C>puEVfB^uXkytKWC!2Wy0000Ne4P8x~ z%nTi!ER4)7%#AF}Ox;{2*D=Y!6j|a_w2Mh?@>3?u$#P6;?8PNPscBXzlew8JCQoE` ztiQ9y>oNlaldY$VV@SoVkjdWu!i6G7>o5DAUfp3Ml*^QQ+RftvYv7$7U%X!&+GJYz zFnQs8N8!6WFRnk}73RPESNvi-xC$@8&(bY~`urj>|l^onn)d zZ9SxNNBhtpU#sia#P(G1$=|D#Ugn@uxcvPc2A;SEZQ~DLBzt-fK2!Vik9Duo{yOGo z&k|lP<_e4L?WliLV8G;k>gj`XvF!V6nODch=EaJNi`OoQd>R$Vy|VxC>CKWcyLdKl z{_xaz&3ev+XaUDHPyX)js?9g&F)!^qaHD5Qz=yi++yAcfzf;d3Ec|cJCZ>M*E`@J3 ziI2}shmR#J+p&k|z3I;^ z(?HQuZqC_U3DMm%50}Mi9sFS-BA!qY;i>!nc1Yq;sdwDf`b`!BSA?g_${t)j-9S^` z$M@i3^*`mgIXi^P6E~DE`MTA)mGN_nTfqx~y8XWy#x`roFZn77ay` zets?!&TeyFfAG{_rIS15eTrhuBB!rhvob4^i=Y4D4~vGo9&4|yW1Gys>5$nhJAP%K zFwuW5vnQY2p3nA8?B9~mzq|Rin@ouDu(1#Ew)xGhJ82bXg*#u|5A_2K7rwrks6MM_ R6ELwec)I$ztaD0e0ssN|NMQf~ diff --git a/res/icon/hidden-disabled.png b/res/icon/hidden-disabled.png index 4ba22aa032f12d3868d9e5d7d1ae0de81c6c103a..fcac0f79e3e1d960ab2f76cd44ea50286b1ae9e2 100644 GIT binary patch delta 1315 zcmV+;1>E}X3&IZ}iBL{Q4GJ0x0000DNk~Le0000a0000a2nGNE0O0_bn2{kU0e_K7 zjwLoNV=*&0AU8QKF*P|jGBhk8GB7bVFEBYTF*GeOF*-0gIxsP@{b2zhHA6NuHa0Re zGeSW#F)>9qIW;&kMlnJ)Gc++lF+@Z)lYRmmAT>iaG&VLeGc!U#GchqmH#s#pGDa~% zH8V6ZK`}%`HIupmUw<<&H8x{4V=*l^IW#dXH)S+sEjc(eVJ$K=Ff(B}WHC8rVqzj7 zb97Q=W;$eVV|8t1ZgfdRJtARbZ(?OSWN%|>WIAwYZ**^SXm4;jG-70BIb%3AEi+B5JTG!&W;#S=bCZe#HGjsh3daBd1KCML zK~#9!#F$-36j2n%&&F62i-8DZaO1O}BtS!g6d5M=MZL{tw2Dr)`NuSPv%shksxYggx3jP&?#M8wVJw zoSGs_Dn(ctK5Vy_L~ORA+;Dg*KNy_F5K#u>!`$Opg*`4{E71!*H42m2M2@qpI-Q3W z^7BumaGVJPjmzf|bYL#9jy;M^7YYK^)zwi|RTX)HLHgFwLE*A8^85X?y0*4yuBg?z z%kOrd$qR)>bAKX{8%i*mD+HKMbOTB-ZnvAdySu5ev5|`7aq@R|5{Et#jnW!cO4WO< zR##tT7=dveJxasF!+Jo>Rzuo<6dxI&-l6MK%fKwp7=dBX0|NtEFpni$`gsTg zS=e_0MwD*{42I`d3oh* z*OuQV+q0G?C;x^qjQA`yqFOTd9g}&lAsj?kt*@!@Qp(}bY zN-LyRc8aKHW?Zq@&t<1`f5dJtO2CnHV17X_pbmgc8_h#PHZ`y<9uMtWSs9I6EMtQe z6?_wbIV@%OLTShX#Hzm`=oLT~vMrB=zbVMA$)xY%moMZ0R*0rcmj=VHH=o2{gMj!5i?=rVXp?YWiU#4NDj57Flvc04fScOVYIdm2GwE4C%)eT Z3;?xE-^?Qm8UFwP002ovPDHLkV1iDGD)9gS delta 746 zcmX@a|BhR+Gr-TCmrII^fq{Y7)59eQNDF~52OE$KJJ%OJQBi}jaH3bAs;O?Wfsvtt zxuu?=sfnebp^k!)fuXU!p@F`UrLLi&m5I5Pq1nd2iHr&+X3lQrt_H>~#zsJOt|k^1 z#%68?My}3omPUqVhK7^tm}FpzjBqO2#S}kTj!BKZxFjew%_?OwH2U++KT-~a#Ip}+V$svBO( zZ%Qz9VfR?enq)d-#+TjYJiGYiU)4N#R-g9c(8-l5-yR;8{C0o8y%Vox&qLD%35pY} zIE`9bTU!-PO{XThm6l~+@Hm+s_b#$Hk?&$zQau`2Qc&&%eI!otFm zIgM;KZ(b(uD10k*`1||&k`^~^O4j}Pb+o`Qa^}yK69UeuvbOGaSStKLk4b8igvVE< z-RJ6O^z!DMn0k7;tyH>?%(fd zq|bj-fNcpwyTp|3mBl&}wAv(-O1|qI-l`oQz%x-_H`U_fSM6Pj3zi7*Cm9@fbUl-Q z*Xm>M+}M!MACr?Ou-5N5ln@2^i_uVH?~wSf7T->TJ% z*~~OHTE^|u`8|2+*Av=|jKTqL8kowywe$0wU*_Xjb~IOkk4dVCHF5&0?NO%7$J1Y$ z=kwJ*c{a^*&DygZJz1_RW_dg8;puqJ#QU@Fo%;mGPcKf&%_yqpuKPLZY5Auof=QqG zl($H9I-NSXz~O~4Thbz)Mp0#h^}5ZM9xjn+((OrTYn=2axrj>x7~%|9pX`5dF=RU>7I=Y!UxflVliSgt*CK;F_L!63sF~!##8IxiBywRT}um-L|r2j z1LH)?6hq6jq$DMU;*y}$G^>>S+iX88s+;isFIyNR(@!;8Sgf*tLIEig}!7E+MUF;-=Q&h?JS)SVw0YTd(825T(s!Qs@%+dU+qiPF1*`&{_9eKW6b-{?)_f* z?)|;z73YdedDxg|Est1a+1+}#tjv?75f`urp>`)d5q_AHEYL(odQl*fo)7zohca z8aw}&C9{70-M8u6$ve_zt$wp^7PlNuj5gwHZ~jxae`Jp~aym3%*qCUAgP8 z;Rlh3wA7_R8&x-lvx!`sUM*)HwQ2o>CXs{F(~Jw|KAgYJt8HX~ONdP4bBU0$vC7n)YMGiI96)vT#OE2l&T@u$4|TBvR6y_Y$9 z@!G|A^Zu_img{eiT6^vNUibRVo~=%eMLT6mcgHTav~SXW@O8;vpXos#B<4t0`P7^* zJa6?56h`uVRXdfciM*mq6;Hh;8h z<{jC`>NiX7vZOnDsDwYY$&cK5_xZui|2MYY?LI&0O||Joj!ggn delta 523 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 delta 752 zcmaFNf09eFGr-TCmrII^fq{Y7)59eQNDF{42OE%-|NK93qM`<);zX}LRa4z$10zEP zb4xu#Qxi)=LmdSp14CndLj!#yOI<@lD-&}oL$is0OD5l8RGOT{m_2zPlg{LNCg~Vs zb8};3S91eH15-moLst_gS5rr4XLDC03u8+QOLHS7g^Jt)UtcSi{N&Qy)VvZ;7h5Hu zh+ax&iWO9^Auhd>UosgeKops|;#6eIECW;IhEq{3vr|2%tBfB51LHnV7srr_TRp-1 z{SO5Q=slY%mLrm80?y0p=IbM zvBpd@sLNY4tEBFx7W|pM5rM(qjwe zBNp46@B@D-E+iGrx^XOxj$2QO6m^XoE^G<9=tv7F+uQi z)C5JVw*Bw6DeTm(xhN)+^`lLKb!WHOxAwyi*+nz9M%~Uh%yixR&lmoS+U;>)R!;O$ zix+Y2lxq9v_cv+d4)N}zaR*JO$BK1sy|1yqaiM5U9b>3zM9iUEN;!9xS=N=t+J}Zt zbvA9^aVWN-`s%E;$hQ<~O z=9YSfrj`~)COQg628L$(2A2AUCb|ZORtA<<28J8|#xcsfnVUHpJG&S=IT;%ox|&-W zyI43GT9_M{TN;>Jm>Nv3Vv>cbamJ};8&iC}p@EU9adMJ{ZgP?t&~_t}6y2m0^E6!} zizGus6Jrx&vqU2$h2oN+)HJJ<{N&Qy)VvZ;7h9#ol>DSrkVs~VRY696Nq%uget}i8 zS(1gZrIC@YaZ-}8uAyP7xo)Cya+zMHislPr@0=#?n|%lVw9Tm$|$YHgjI8%}M^FJSLGD7m6+^NLK9c?UToj0`V(mE~0*+Bxli_6vp> zrgZ-49g~aYN~}_UCtTOrefp`;!T=8o8NQ!;gI0E=7-@DLP1>C%^?X_Ov3aLB);4dL zDY^Z&?xT-8>MxYWX3SDsXmoUipJU>?)k_uUKK~h`8f_pkMXhM(oDWrdg|vcuUWt^R zt9^Br=Wdg3Lh#9u`O93C6up*CYB}|3`paFb6s5mcUY;KAuAeu*w@vJT=8wQk5AGQv zoi0y0B-#`gU0l&K^>pghfAu@gA5Cl1=$6k}k>a&9sK~`euKz@R+GeH6Cr^N^50t)> zzVq&s*aqj#K0j;XjwEfgU+kFL6*#x4Z-@TU;uZVemjAeuvC3(o0#l>Iaqr%gN#M|>2 z96DduSpI=a?jW=Dj~CZ#wO%Yf7!$uFvVN(q^E5?Z9KBdy$11@f{dCf6?LPs$z&yg> M>FVdQ&MBb@05UaJQvd(} delta 455 zcmaFQy^K?_Gr-TCmrII^fq{Y7)59eQNDF~52OE$KJJ%OJQBi|2bD~$Ds;O?Wfsvtt zxuu?=sfnebp^k!)fuXU!p@F`UrLLi&m5I5Pq1nd2ag6c?E`}E7E|vzCmac||t|pF_ zCdQVQE-vO4&d$chmQItam}H@9EO4sX#uTq>WSD4XkYbjko0gnrplf7iVy0_hVrZso zW^QR_Zf~BD~U-ooy45_%4^ymM7duCGx zVdkj}S9y{?e*G%@Cr+60k(0tb#v}d>KdeRU8@oiEu0Am3Sn@(q;5*|+YXNU&Lv|5w z=cB(CWN{TrhH!))VHNJ^=t#J+qfl9*`G!XW_o{6ScNZLWxYPaa&WRHqZ8K&_7_e>J z*kHFJZdC!xH~WGw&XyY;YWT94W!6M&Y)U*iNwvUCEP(0q;TMk|H|qql965X9VGDB& z^P{Mg?#O~4l24q>6|OmUy)t*GVeKe)yt>ED^JYX&HLvN0{wWPfGW&MU&thX_kzlwJ XW)^tG`@=_|{}?=7{an^LB{Ts5bhVl7 diff --git a/res/icon/load.png b/res/icon/load.png index 5f64e2cce43ee759cc9acfbd23d9d004a9f91e6a..2e3dbeaaeddbfec8c7c3f6cf6f3081b3264c1455 100644 GIT binary patch delta 1106 zcmV-Y1g-mt3+@deiBL{Q4GJ0x0000DNk~Le0000a0000a2nGNE0O0_bn2{kU0fdoC zjwLoNV=*&0AU8QKF*P|jGBhk8GB7bVFEBYTF*GeOF*-0gIxsP@{b2zhGBGhQLPRt~ zGea>rF)>9qLP0}0IYc)^F)%_zFfc?zlYRmmATlvAFhWE$Lo-7$I59CrH$p)}IXOf( zL@_WzL@+Q!LzB7!CIm7tF*lRn0y=**GdE;mV`O11GGk&mEip4RW-VefHZUzUV=y;l zFgP?gWH~k>Aait5Wo9~LZ)0_BWo~pyL_H#5WN%_+I%IESX=FNZXm50Hb7*gHI%8#J zG&N&6H7zq@VmU1_F=aO`VKZZ9EjTt|I50FeGc`0}WFjv!4a7&H`eV!QvoEAf5`u zSM%2I4+LUHS~>_M768PHf%rTS-^GHXp=#BDcpVTQ13Jov77o&eI$}K##{jVb5G!H9 zd}#VCfcP-bQLU6Z2ssUy1Mwap<{^L85IzpJ{|+j$cdQhoc2LVfTtK`U<``CHrhjhg z^8fTCMgQ`1vSVmkf9W>M$wx1k*;$wwoK@xiIH}71VPj^N1xoD#;w~VrzrCr1CZ{h$FX$h`w-li`01sMCO>WF;90D%vryaPU(ULbIvqz^; Y0BQK~G+<*A{r~^~07*qoM6N<$f^4_Z*8l(j delta 648 zcmaFI+sUoi8Q|y6%O%Cdz`(%k>ERLtqy<2jgAGW^fBv61QBi}jaH3bAs;O?Wfsvtt zxuu?=sfnebp^k!)fuXU!p@F`UrLLi&m5I5Pq1nd2iHr))uI2`ACMM1XmS(PohOQ=- z7H*DCPL?LFmPY1=E*55!>zHIih;88Q%2Cy5Csv)y6}I^DBMGUIjqgtt8rivk}!{q_I06U@>F@4#aY>1P zb*#r(?l5e4DHG(m#wN)nb>8;v{fj;vWOIl~^qZpn@86#jAAWvycDWHSYc3-b55toG WQv_Ab=hp!f7lWs(pUXO@geCwH39qIWsawMngeFFh)2sFf}nZlYRmmAU8KMI7LQ8I5R>)HZd_pH#svhMn*$H zL@-7;GB7nUH00XZ{L_t(|+O$_qNK{c2zHihlnK3noXk#HQq+A38MG%3|rcJ9Tvlg`p0zohr zErp<;P3?+SZ6Xz-*-NH}B5ljdN$T1K)dZF87}E z-E+@5_l#xRwlbBc2dp~0T7V|t7!ZF2wtz8U6!-}A+?usVOm$i&1jO(I;1W=hXL^bx zfIA@ad&dM|`~qGtfCzs_ETt+#N|jp<6Ru9!jvJO>`md*M8Mp}&T}2as@ix330U;WW z!eVKj9VLM2B*-{w`MeDhj|(OMfwda zd>&}#c%_vcaFd?#uT%uILD_#hfVtlZ#DLzUxZV}&sI)S+noW+NDV=UU*Gqb>0SwM@ z7|ytk-@qz9uLDD?ax|`Jx|&P~H1bid1(c@jJusph_X$b7@vYHyHJT8pRlah!;VJuW zkbz(LSl0+>x}v68kR_Ue8iVOoX~micH7=Vb1b+HK9+oExYr?Z0=R)x4Fwc0&NvHPq{k|>8Jezs|GJ>k{t{?k^MzRz6iy`5pLbbU zV32iSMLb*%oCBV70;{L4Sku*IT8f$Lu_zQ(YdeW2vvxBC3`&+B0ZzHMC>McOz&E5)j5WVj zQyPYmaETLG8!}d=GBJ`Rv#Crv4gjx%34=h%fPaoeb)!|fy<8Tsd)UFcHs#zZ!&j~FaCcI{} zFIUCe(O)y)JyB96O~+B`_1t!iSM5pce*V7_5E5*yKD&R{r0+isyl4+-o?xn3F#e=i9sdLv08Vq0?YKb8TL1t607*qoM6N<$f^s4dLjV8( delta 589 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 delta 324 zcmX@Zd51%>Gr-TCmrII^fq{Y7)59eQNDF~52OE$KJJ%OJQE|Jfscy1?k)eXQrJkXw ziKU^Tj)IYap|QT9fxeNYuA!loiMf@b*~Y(#j0zS8E{+C9M#cuNMvjJtt|pdl7Ur%% zY;0z1ZffD;IJu5V2Byder=nd<@ss74)F#_AH`E_ZYq0}5rP0&HF{I+wnJI=`2NZZ* zeKQvZ`@i^TlgQ4S<{@h3F?;_Ot~5?NJAP52pwR1!#jdZs_RYbiI`g)&a(Vs7Pf}*C zrf>GzM+-PvWU|+bS_PxP$MbLxZ_7fpw~$rB|cX}HxTFIb^BLlN}oGd m#45|xw@q7i+kH9%Gs7=upH=61N+$qa!{F)a=d#Wzp$PyxO=I=| diff --git a/res/icon/perspective.png b/res/icon/perspective.png index a40ed7f81f89ecca2b405d38f9615978ba14fd88..431898a3e094d69b17b27984be4a4d7694700682 100644 GIT binary patch delta 1780 zcmV9q zMnNz&MKm!nK}9h_Mlv}>L^ee zKV!$6)Dff!p~9`2Ac$H}QE5q{R+UOXZN-5@{sTV51qmUpT#%5sP$bHs;zo}>RBAv3 zMJ0_|A>ky1l2oOojglD0`LL7SwY|Hu!#n$7uQzt$Bvk65V?CPndS~Xh&->2It~q~7 zDLhHV2Txde3bb7j_S(U~aCT4aZw$8mKR^i5h1Kh*KGaG3^f+n^bqh83SRJYX&{g9y zc5cW%p4$zeuv>rt&p}gwHx)&(BB=yqTPmSly8+~!b!evmL1F9pP|^)Ap!bq0K+L7N zDxi-RKlnYpcy?iR_2@oA!iU*!PRoDhAP=#S0P&yzexBP=PG|^{FX^yRB(RRPtI&vA zynFoN#L9&~e(pGO`QocpA&W%j4K9Iy?Rc+1wn?8LnyL_TaQ{z4BB@#fv0+dX5 zq6ipAz)&5Ris(+hsDp%Um`kprwkY|TQ}=GmZ{Ha`l#o`t>xS&p-1!w&xA1=dp7ReU&;9vJ_{4+MivlIb%CjgI10fm(zbFD93PEmSV(VE( z(3J=;zzcjFvpNQnp+Z`dAfwBW(F%cR;^vW1;#cONA?IJ3*+`#TRr9@_0y6c^-I>2I z`D1JE!t7y=ZbZK4QLHv0Od$lvAnfxEuS3)ZazJ!b#QbO|c5HAIS_1)y zVPbbe7?!mH+?Dg@^*}IhFdv)t?+1T8jy+@7Jg!d${-*2JG0Rt0b6g`p%pEIa6(9p) z+_i4PS%8L_)Wr0ZT0nnT{0I!1W)1EY&)ZvieYeU$hAUi-J8o$%7|s4}IcVL0>4drP z)PbqW4f}fyr(6zM6J2)!X*FG{hO->1gsd)5J*T#Eh?qc*E7ss@O?Qm)vE?e=-XX^o zFBog*0`r87dcstxH5Zs0nAo8%1L^wafwz*=u3ipp4iB-vSHOQ9*Ho`VYybw-C`=6~ zp|T(vxC2okB`v{o;U;r;SF7BqfVuOF*v^)%C!{)g>qwVnC@e2q*ZX`*Er$jX>e8}a zOlCF|b0O>uhpR%Rc|vKfO-{(D&w_H2dkZO<45$cXPyZAS|K6sD{O`n(6m~vH@bLXjyS=*% zN>OszWGWe-4VC~v%ZQ$%-<}6|^U|lK}p(sf}*?FJu zA?kf`Z0xs&UW2ZP<0CkK`f#9YB}(9tBBLZwsqR@YjY@yWybGTlK8nkLys`p?wKbrY zdq1ECYQfZX$X(vby)uM>`2xF+33$WJAszyLS_XQQQ_Tf+q%Tx)$az{Um7svnI|dtr z<5p20i>cJLJ-sMi7gJXdrXO9nRlqx3l#8&Wlxh3iHHT2=5#VziTwTY<(u|}*k&yC}rBieak%URBV$QHO%y%x!XRuz5Zc^P$DQ+U)n&QAWVKQ{c%tKLEEPMC%^z` WXG{acKPr3x0000tW-czK&Mrn~1_q|CCWeNV zuC7LI&PJ}LPL2kq7A~ff?U-Z~3=G_iTrDk~OkADJ4GmpQEG%3MT@BofU5%YxoQxbT zCs!d9IpS2bjVU1^$t1JW+r6r{qniwi66qf|0 zrdg%rCzs}?=9PH5*eX@z7U-p9rdSblZU?hd{h9m9Hy9Y0zInPhhE&{I6LQ)+Bv8g~ zy>%+R_>d&%lLQ4yd`P({nvd+>lF$T=Kq)yBKoj_DKNBgfz#jFGKw3X7HpYj zeA4Th(o3ItLObutm`u|vnY-q5@N7dp)9X|AFLy_}=c`XZmrARH^U3wSkLN8Yfsk(8*aNaN8!a<3aC+Hcl>fjfM{?r<1(Q z4o_T_(|2ng?{n?_^%rNvxlM_v*?vROl+AteTATFs-L1X-2hX0}9Am+;YSHoD(mLa; zrZR zp0B*&?&EKFToZN)@Tz(#UMst`bm0aLl@j~t0@)yiqKU0J6NT^12v4raQ2%lE7x+YF`6=Vt~fPUqU$E#nsBFl%1@mt2LS znYI5IPYdksI`FB(#c(m>!-kYb#qtyX>Rc-gj-*}O{%9$$nZm}ss!N`KI9ThNnAf;M z>5hS-=vJ|M9fhYmLWBMbbS5hJH}n0Zd&~D%m8f()KCe5`)Mp=S^+PVrxz-N^ zWmzt2%O6|UW1}Z}OeXJ!+zGB?)s9I^zaKiB{N2>o zTfCz!?L$(Ntmk#f=fR=-fBlMmw`c0B1I$Tci%%R?n$IeqR_B|WBfCOEcE50^OWnDx zkB`>J{l8~s(wl$!-JGe89}haVB=O{){2B7s`h~8TKi_ZpUb6$CH+!cCq-d~67S(t! i`o7_Zggp}vgUs2rH*c)rdj>2S7(8A5T-G@yGywpZTgcS_ diff --git a/res/icon/play.png b/res/icon/play.png index 0ba5f48c9403d8d2ee883b59348ae05a0afce9ad..d4eee55e424050a5f7f017023d127171dc8aa746 100644 GIT binary patch delta 645 zcmZqT{==o%8Q|y6%O%Cdz`(%k>ERLtq*XwegAGVNWSB8yqT+T{Gu>oEV+#dyOFctV zOA8|t9R(u;Lok);Zn1cDM7_N(ZC?tBGp{iFwH1gH!;b?OxMypDNQ%U+}t?T zBH7qD#lUE?Ept=-lD7HW3=E75JzX3_DsCnH`TyVkZ~~JLPcu)CRmK&^DNJHcTuI5v z&ly%GD9qzJBHJyl|If*r-B9S%w~H4q);EY4Px&p;ak{bM=%uCJ|M@+p{QdiTx@3ps zf(WsW>5LyAc64wYX5?IW;X*(R*FVOOH45t@_EvqB-eswwq0td=rM`yYk(k1~EqQl$ z-8bpqWMBVJ;1H9;9p)qAfByWj$&ho3i;G*LcF)%#W>4FP6!OMl#pTZ zI`eaT{(U>w!q3bhQo%FcI<9S2{r>*Gx>oUpQ;rLoHu85w%iGua1RXqa$f0(FV25`D zpW^CQ3zjvnYkXy}z`2a4WVY0mKN^flwO+E$nH&eDGaT9j);7AWYwlw#z+zed_6%WPQ{bM6*x+gA$$*#kKkn0Oe@7T&6H@(z&( PrYr_eS3j3^P6ERLtq=i73gAGWAo$CvqsJLC#R5#hc$WX!DQqR!T z#M01EN5ROz&{*HlK;Otx*U-?)#N5izY~$ZVMtO4=M?+&v69WTRBS%9+R})8L7efmd zGiNg+S4#s|*U7a^vQRa~Zn)I!WJ;JU$D}sdp1GmEm!%|;fq`+kr;B4q#jP_T`~43& zi0oUvbeqTw4cO)IF9O_?V|t~;giDO{{PNWJ@}z`fePTGc{e*;7 z0&_CgZwYx7-pHYAf5qU(r0cKu?*9F0@B4e!uXgFpY%5IhI`-YaXlKld5G~y$mtV3w zX(-7>-->-}Q2&2cV9mtN+jkdhoKgtbd~?oro!9#!bmnn4ZJ6n!Hvj*T6DGcoEcn>t z8D9Xk+jSjHx?ONB*?7f^autS@+hzG$jdPPEj;&g?iv457AB)*%U;X{f-Fm3N&BCSN z#`&r~!)^QR^K&+Ju(_IH;^$HyxTiM!>YfAb>n4U> zmE~K~&+_UP&$Z37mg%0Hc%oOOD}Bl(?;~3svV}PePna8FvTCb+%%v?iUqokbjjCf1*U`H$S^8nY zkCWMFw^!K)g#5EwWH&*nM%Jy4-5|+Gc*Wdp3x&0AH&xur7v0Bd@4D*k{1yIx&wD6M sj5zWkVr9se`z7{&%imvI&cMvTB5*aox!q?mF!?Zey85}Sb4q9e0C<`Gz5oCK diff --git a/res/icon/redo.png b/res/icon/redo.png index 2d86e0e36671d1699633d35c9e6635288c522929..830442d8f9102cdd055307164a406adaf373822c 100644 GIT binary patch delta 695 zcmdnXxrSS@Gr-TCmrII^fq{Y7)59eQNGpIa2OE$quB!SnQE|JfnQpS7v4w)UrJkXw zrG=4+j)IYap_#scrM{twu7RPIfu)s!;l{s-j0$e1mgYv5#>U1@j&6pAuI5gbMka3N z=H@QO1{SUsj+T?_m}FpzjBqO2#T2iSoMf45WN2onn`~fdqMMd#VxpU5ZfdM+ZfKHh znQCBekdl}(*_OGf{!XPYCj$fHMNb#UkcwMLfByfsKkUFHCgJSso9iHRz-5UQx57Nm zBi9>#NDKUDdc^LqN2r6jafV>`yl#gxUMCXVB19f=Fj^|iV>@z><;+foBQG6lxIB4M zKG-RgshP9*H2jbeoYvgHC?L=BNW-L1U16SkAM=yO3TXlE$pUj_>K*n-bO>KO;`k@3 zsZ)TrAx5`L?HgCea;A@h3jcV&ma++CFRaYq6ZnbY-+Y^qOb0qsE zcRK8uHH+^?%hD;Y42HZIcq<%kBO^ z%Ycu$P;86a;+4uDrbZMm!uC~t2};IKRy^ZSzopr0Jr!0J^%m! delta 421 zcmZ3(y_Zw5Gr-TCmrII^fq{Y7)59eQNDF{42OE%-|NK93qT+T{Q{7|(BSQsqOFctV z6H7xw9R(u;Lt}kI1AQY)T|+}F6LTv=vyFcf85ImI+*}<^%*{;=OiT?8T}>>E4O}c; zO`Odv+{`TCjEAl9(9FVA8dFP(b~bfSS!ZJ`Oo=UL1t#%0 nn7cH}FekLFNS@5VVZaccXf$a7 diff --git a/res/icon/repeat-all.png b/res/icon/repeat-all.png index b9448252f4d376f642e567da5cafb8cbffec7887..69dd05b6fbc0e7f01e75962e9eca0b34249eec1b 100644 GIT binary patch delta 2022 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 delta 1081 zcmdlk($A~d8Q|y6%O%Cdz`(%k>ERLtq=i73gAGWAo$CvqsHnlHIMJ(5)l@gxz{pU+ z+)~fb)Wp)zP)EVYz|dIV&_LhFQrFPX%Ea8t&}`!0lF4@%l_qB~W>4P7q%*mmNjk>G z$;`sYz|qve)zrn%(AC7k!oUTT$SHF8dr&zMpsWFhP zqCrQ3=hTPv|EJsQkN)_7oH=Vv*y~!c?yDiQ{(mj}ClSsh^g#W?)|B;y7Bc0*E2lUr z2u!HuKf!R{>jb0wLeAoyF@K*tS+afADy=1#U&b9syLtES*4JNuO)r?E9ug9A##?35 zh8Vs2ySH!uHkf?U7+=T&-IS487S!jlbT^+rHSP zNjNz<+5B!^IagI}%a3Oc3JI^@zrX%?!l7Tke#tGm5v(=!(5_upcfWkuBHGTu#?C%p zh`sgP5znbtuU?f;@KBnyeMg$eqH`ZB_81&X%;~t5v+cM-R(57)Wm8qx!D&GnBAoNj z*Sqi9*tKxw)hzew>fPOE)56^r2l5_CdH%W5EOz=KGYOvi*RNgs6SUGK*F;KvgWk+p zv#R7fU0hbLUj4Ad>hHUNGp`Gz?%ud@A)!vpV*iE>1+$OH$jQ~URSGLCt70xu(Nhrb zbXmOR`-Z-b6;n4c?O4H@@K{ez@0OLd^8?>y(w0e0cbLnQd=xulR|uJyP$y z7sa!M9MKZtl2n`QIpb{FX93IITelu+jDDU<+W6QYdmf zwvAQOdE-Y3=ZFmxQk?H8NqeVio^=wOIl)~>HFD$B-ueS+pE8Upb#j*6s8rKYnRP-( z4PlmyqD{3lPRuK$W+vb~}&S`GibU;XB z`doX_n(x9f&CUh-w^F^Df*iy%SXC7!*O;;B?U>I#&2iy1S-!G^QXBz}i)4x#Dgy-6 zG6cmZuP##j+|*!*0y4aU{75zZGLuo)05VXGk?f+9|^dvRJU|R zP`JQ->DFyu9scA@RG4+*p9aS!;s4GmFM5Cc3>3KH_D6c+g5pQtBXpL@l}vuj94LEB zvGeSv`iA#x%Ij4#724}%HlMn8=xB@BW6n34UyZI!e&x{+{owM%;P-ptLq9sLPFb_P zw?TX9wT&m3Ej*rC!?jy%iZJrSN(+crgq-CW7~Bdemat0r60 zK4N_q%hH;A6Qg_I{1EDq)|AY8?5^@m`Jd#BWs6;2ux*^Nvj1R9jL(|1SLV4J4$a212aR=pY?b1oEQBB<~0USS3j3^P6GX) delta 699 zcmeywJ(WwbGr-TCmrII^fq{Y7)59eQNDF~52OE$KJJ%OJQBi|YaiUkBs;O?Wfsvtt zxuu?=sfnebp^k!)fuXU!p@F`UrLLi&m5I5Pq1nX0C6n(kDoxH}%$~fDNoR6BlT5U! zrKzQXsgZ%Jsf(eZtBHlHGZ2_rIGLJSS~yu4Dk)Ut7Wn#Fx#TC8=BDPAc)Hjs0Y&su zGE=NvAbO2)>7D$N$v^?B$N;AzTV@%UB14>tYMGttQ=ZMg!oa}T<>}%WQgJKk&;S4S z%%%*=TX~WgBwcvoHl=Vf3kwTN>s2n|xw$P@`ouY};NW1%E*_>PE`@vciHBM&n%Ed~ zQ`xk(u{^r!U?Zz>tn$x~LOTKJ6b_Ef8#lgv{^${tWxk<+dRSQ4ro|E=p`k~+)*L)? zB<0W|*X|pQ^>2z4?lJPrzN^5KlHZuL!AeeUp5<)w{Io`C7M8+*lX?qURP6GFt7IEA=a`T*QKPS>KK#tf;~JkL=CF!|NQ*?TwS2~$&Q8@ zljqO>|MS+??CKdaXWIY&{oTJ~`t(?*)k7-P(=7Cov8|f&{p;)NF6mjv9K+=pStJ;q{oz%qsA5+Frbh-( LS3j3^P6&68HbFTuK{Z4(LP9k$ zF-12*LOC`$MnyR>G&VRjG%!VzdIB6EG&VswGC?&%GeSZ&F)>9qLP9wU%}e;5@Z3<%OV{}43#k26R#5sgF=$Up)!`-ctK0$WB{VF=2PLMc$vK@0EoJFMcUGMT_^HhYDs z*Pn){utbnxU{v8pUVbm*P=9P~NAF)aWevrzPnlZCb8{fEFD@>o5Z&F~px5iE$e8%Z zwe?2MToR6fAN6UVmTJ}~2;$_rOCwi5Bmt0%Yv=|lUz}54=1x;}bv0P6Rw^>4Po`FH zfZ^CR_%!1&M5>30N8DTRg7VdOaUcu%)*(K9x?w)cd<;4b2eAJkE`JJbo1kV_$4I(Z zF;E7ok)%72A14J&thBV0MkFdK3d+jLsBeCL-UCzOhH)E#Qnh=a7F{FGyoTRi!{Lgr zE+dW>e?Y0JsZd^CPHRRSm5PcA=1 zDBV^-)6>(mo05~0X@7UQBV#lg;ZE;uS~G7Y_zrge%-ndEu{m{ca&bmj&He87WMTY- zR$UM^Pp(6(oV?jsvyv;cP%^(X3Yl-=cW!G8yDu5Jl4YhyYyt_)os znsm(Z-~P&m>O8cF$A?A%(6gIYy?;`hotxJKqsYhTp; ze6t??%P3MrEW!l+U(a@ccr5VAzQM^tr_=ebOeQ0Hku^2gh=ddf_p)KRTt4b>I5=VZ m=C)arA!+-k``!LO0R{jcJI5EiWvD*@0000rH+INExp&Fh;CJ9wEEJo)td zy8b%OLdCVajgsdvx3W0!HA!xf=%~KEVlapu6O zBZoFx>b~O)&{V&;@tbtUf%o_KOGJGtl~l^PZ&0&iBfpTD&wt4ul@bpVx5#zaJ2FcM z3itHzl$MtA?Ay1G=kxQksU4;D>B$#@e;oVp{Havi`J8fzIzwZdkMHj<`~39xfBzR7 z!&r`-J@Jr*Pk4cM)aS1c+V0f-nf~yz|2pk?KmF%6w(VhNwWwMn%l_!5!yl#$24#Wb ze}6x>?_KNA&zZrYd`7^`?AcqsnflBN+y1D|P-wh3`{%!(m8`k{?xq?z@34?Cmr||Y z%b> je`CU4H!cPa1BMsr7aNp9KE4MgP6kg`KbLh*ObJZ@o4_Bz diff --git a/res/icon/save.png b/res/icon/save.png index 9bccc4adf68f4506a60ec88f94f05c762e14823f..d8b8f3d2d85bbb9213e36f691a31cfa5a58a973e 100644 GIT binary patch delta 512 zcmaFOewRbBGr-TCmrII^fq{Y7)59eQNUMM_2OE%l$S`BZL`4n8!iipes%E;$hQ<~O z=9YSfrj`~)COQg628L$(2A2AUCb|ZORtA<<28I*=mQ0?-m@+wwQM10l*VoFwC^J1X zFEPg@Ke;qFHLt|e#a5{zw?Ho?GsP;&*f`D1I4wyx8EBlYv4M%9Zc|rWU%UMkz+RW@#oCx)!F1hPoDsM&^m;CI+cy zsTPwzGRdeA;TVB~OM`1P>5!;MLP;&y)RloiWk b8yFe7azrPc^jlyGbPj{3tDnm{r-UW|`$_i>(q+ zL@y;X#R{s|0+-&&FPRJ!po&a!DzaskfhjV>si>COsV3LPegV+m37#&FAr-fh{`~)M z&uq#d%siEW+w5Te{e2Iw9yxln^}a%Jaq$lO@9*#PiERLtq*XwegAGVNWSB8yqM`<4%|x$0RWsdWLt_gC zb4xu#Q%egY6CDL314A=?1515F6I}yCD+5a_1H+AfV;L3PTudzt9Zg+~og7^a4PDJG z3=PekOWNc)RYM5%Mo0w>ktec!_VxgOqm~5b% zmS$*Znv`msm}G9Mq)=QEl$vIhlAm0fo0?bR>0+ytn3A8A3KGdou`0;OFUc>?$S<%; zGd8kFHMcO(HBL%Q)ipFsHP=lvPEOOcFiW&BFflVWHAzfS(zjEv(Jx9#vvNr-o?Opt zTJLyM#g~DB@uR1UV@SoVHxY)}ha5!q%~^U*>N?-us3nrSN;BTp7jRE)a=zXWy_|!$ zq`2TsSWC`3*{!m>L|vBDJ3YSBbnJWIoFfywd*W1dD+`{~OyBeS&%NL8iUMP(l3Yq(4Z}bF3HGf2mr!GV*>nnXmvQTQ=Lx5?&wsB? z;CUXGTl+_Hs_Uv{aqI6Ne#&~Q(C1Bj2LIo>|E6xW)z>dv7E9D^nJODb4@)*@^*pWOuy1QL)doSXSn?O>y51!Gd8?V+Nf|fYwDILU6%7TeUB&j zEq7L%%&95D^=Qih`KO;x1V|>kTsBFH6blTU%6hb|_RB+wbY0V_g=rQFhYch+Vy~OO z+nhVQ*L3#T6<1D%N-{ml*t1WyLUIWt9b8ZfB+`XLxyWjkRtFf%ZCy85}Sb4q9e031h1p#T5? delta 535 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 KMNUMnLSTYppl2Qc delta 1097 zcmV-P1h)In7@rOyiBL{Q4GJ0x0000DNk~Le0000I0000I2nGNE09MY9SCJto0cnv+ zjwLlMV=yu?AU8QKF*P(fF)=J4GB7bSFEKDLGC3_VF*-CiIx#k}{apbcL^3u-LNPW$ zK{P}-F)>9oIW#gwMmab^LP17DMKDE^c>*0DL^3u-LNPWuK{P}-F)>9oIW#gwMmab^ zLP17DMKDE^wE`!T;Q~1=H!v_bH#THuEn_%1H7#Q?G+`}bF*#!`Gi7BmGB`M8Ff?Oj zA|P{gQe|d3WRnX5G?R@3L4TjEUz`8{158OoK~#9!j8ngBWknc0Gw*>Y$qUG;m6e)R z2t+r91OgUT!n#fV0TU|`S+NLlJ1uN1Vj*_BuC}Xf=c@0}O&iL6E{K zYSUog8Z@dBsLd@H{UdxhJO?^*3SBAQlS1}#qNUg_wrI$|pum08;KSW<05rlaYUB1j32)1M)RX-{1oL11aCp}mzMm0X3!&8u(Who67K zz2gl$=pV5G#eWXc1)zRaQsM9&9hioXNCXkAL^NkWXB8Q&{kDpa7T-Zp6!>**4Zq*t zlET<)l;tjEDZ3~sqK}^oAFi{Co>Z(z6GT~xYKC*!XsmB-p=laSO-&7>EX%3w?d?ww z4i3H`*w-?C>r!!hiba1|IJ3pHu1edTwTBrnj`T z^cr8E?{r>$Y3PDb?mjE{Lsa?=QhH3PY%{fDHCeAfhn}5yvc2;1E5duB`{DlUo12?2OioTdx3aQwdv$d+ zgFG01%#HYegJW!VcJ|%n<>k*fzjE=LNa^C9|2^o?7#~k_-Ok1QKLQK@4zQqu!TM=b P00000NkvXXu0mjfo)Y1Y diff --git a/res/icon/select-verts.png b/res/icon/select-verts.png index d4321c192e3a8c6013049fe1d7c240f804cbb287..5474731d42b9fdcc77d04f87deb569805b6735de 100644 GIT binary patch delta 1292 zcmV+n1@rpq3iJ;liBL{Q4GJ0x0000DNk~Le0000a0000a2nGNE0O0_bn2{m3B{nT% zF*7(IH#sgbH90skG%O%8FfleSFgY(VG%YYOIxsmpFfp&6PK{ho*LqtR~LP0|@ zF-12xK{-T5K}1F|GBYqSH#S6*dIB6EML{+-LqkMEGeSW_F)>9qI6*l?MnObIF)}kS zF*i0ulePk1Bx5#XG&g2sH7z$|HZv_{VKinfIAk+1EoC`mV>dE0Ib|?5W0N@pet*}U z9c=&r1R+U8K~#9!>{ne(6jv0!1DnA%1>%Y#XizaBG14xS2TViKgcl|16I9UHga=~V zXDBHNFC;wmwP|e23o#`oEj2MBKgPJcIVc5D(KmM4?ZPgyF1sv?JNwt~%-&76%gpYA zGl?+Z^_s%(Iz7Z0M1b=&%N&$Pv$_GNZib<>E(pZ6r+qb}Fpq4j32W|qt z{Ba>JXPbDh_JW#IW3nT-jRAEU>pcN3U{aR`nLXR@AjEezlblSj`yGw$nAdWe`U(#$ zES6Ty9A+$W9FGe?BPI>&X|Lu(-=rpcJ%4S#>}fm`mc?QM)u|1AHjJ?V*MIc3S0i+V zF3QfL`$|LMFblrfRzP)XL!T#ote*C2gg&PW9dk4uXL2mcqR}LjI@)61`cL`TdcE!U z7WV(ST|{qtH9~VK2Q(EFFtO0YOr|81I@$`$=AWg7v99)NgeXriL_>L@>5c#6q9XRm zzK;}8o!Y1`eXO4LYJ`4GhJR$@gt`8=4Y7PiDiWhX0@cyxqdtsz%E#(zzt_Y_IpjPg zHbD?}bbfV5$jkoEiwRfe`hu4H;0f+K;3{y6H@^ga2d<}5zZxM^Scn4~X0!RwS(M)1-Y0ddn zWv7vQUh~MJS7`6->`XcFBajK?{FSB*%#&VDJ(Q&=;SXn)MNvFiSy_1srxWPu=^4gp zDNYR7RrSc*AV;B%v=0mn{A{&a9c^uGKhQJKq_w`uU`2W320lYxdV$l4jE;`(aAL#d zhf1L=d^jzGP15&v*YZPW30p#+1l&w~8W~ww zq#Bqcr%twHuB%_!YI%c!fpNO0i(^Q|t)xHy|JyU0G6*wIW#I04xPNvhuSY3!A&-DD z)5jzQy}K`dy%#t9bVBdduj2=D{yr-?ldtfPHHrD?td6PfW*Xn_zVw*6eZ9m{M~NeB zT}y?u`}KPyI9a%r*Byw{N!j&h;)Wal!w(7Vk7u~a-J}BbYkq_IWkCb$B zO{tgZ$nKn)zNFe}SNZ#UeLEcjLPDM#KXKwjyI_KWxS*H1_jJA3w+|i&eERpdy6yJ% zeEa?N|Nq@n_n)`s-TU|U3?`3-xvQ)g-CpMluuIIicEEsf>0~Z}|35!JZ{Jtwo_*Dq7qulNUzipgbZ;zr@9F8jV8(3~4UHAcmn~by!}EkS{ro&u$xjSv!WYdX zI4X2>bvJ8kX{j;XbURU`qVAEv^N~T*aKXOn6OR}e*%;0)RbQ)oV7&)0MKO4~`njxg HN@xNAsdxMt diff --git a/res/icon/silhouette.png b/res/icon/silhouette.png index ecf3b67678310a41dc3968e9c630c87d3285f5dc..1373adaed731dc27bde4e8199e57ed33428bae60 100644 GIT binary patch delta 813 zcmZqR+{vxj8Q|y6%O%Cdz`(%k>ERLtq*XwegAGVNWSB8yqT+T{Gu>oEV+#dyOFctV zOA8|t9R(u;LoFRfC7_62N@j|ci;=5^i;JVNv9po6 zp`oj}qk)OJo0GGfrG=%Np@E^vyJ-=>scCLBPuh+!-385^5 zl`o%*hUi}vHs#=8_-H01vsrRgdt(?M6JwZCG^2v4&XQw7mV8Z&i3)a|M-`VhM$C#h z9#DVTQQ<=CQOCbeOcGZ9Aq=f>!mr zvQOF$J7zLZJ*`q`;W1H&-7v`D#sR(~_8U4jI~GgUUS*g5z*w$e*P`4~#;}q5ME;gO zBSQr}mPe;u%xvE=7Fr8P2V8bMb75lFSCMZY*E@3uhZf1IaF{+&*YR%LawSsX)-y^RSb-547mx{f>ul1kpSiZ22WQ% Jmvv4FO#r(*9o7H< delta 498 zcmdnV-N32X8Q|y6%O%Cdz`(%k>ERLtqy<2jgAGW^fBv61QE|Jfscy1?k)eXQrJkXw ziKU^Tj)IYap|QT9fxeNYuA!loiMf@b*~Y($j0q+NhGuDoNd~&ch9*h629_okx|U{1 zCc1`ZsV0_|X-US$hAB!46}bhzzE&>z$)&lec_p4Mwn{(|y_C!pD`!JPQwt{pHzP+E z3qwO!6DI>hOGhVTHzN~6Ljwa-r^$6pG73;dt~eF#Vv1KG#wC+&nPrGmSj+5GFOcnf&>@KWVBYBfS zAb|Z*w@Zc%|B+b=yf4@q8gvBek1hVmD!_e6B-xljubp|Z7SpTLqy+*Gj21L{7|w}% zaP(14o|~H+TZ+#K?(c;gI*S}_#l){TC116Ek;FJ{LILBHmKHfS5!KX9#=_iJzTI>< s!}gVlUF0#7eB%#60e_Z41|}YcPp9T^GH8}M0bR`C>FVdQ&MBb@00C2y4FCWD diff --git a/res/icon/skinned.png b/res/icon/skinned.png index 928cfc70b5ad4fb16776a3d7a08621da20757d35..b03c1434ff3476d7b208b62b6700aad4a509916a 100644 GIT binary patch delta 2080 zcmV+*2;cX>4T2USiBL{Q4GJ0x0000DNk~Le0000a0000a2nGNE0O0_bn2{kU0fv!E zlOQ-CH#sgbH90skG%O%8FfleSFgY(VG%YYOIxsmpFfp9qIWR7pR>>3x7NS*3-0VydaRL(*+-b46(lP|v+8P5w`!IcqR_A7~a><)xjz^s3hni66X z>)`NNV3<&mf@96PgvYGVk!pUAKR4{mUqpmp*L=*Y7Z7eJ^qGR!EpwG;|9Vmf&k9cMX=W z;~JvB4iCR{#2o&0AY{JQnzF{5C(lLQ(M*d(%uaw8%A$S?Pd((&4tVJc%?a{a2~D?K z5>h_cpPY3*el~iK(lC#@bQHJGW`s&wg+od8Si({(pjyZ*r!280JhXr3vaE)Qy#-Hw zEdGk|w*}k=M|iKc(iUy8;yBYdbZnEej{$N|4T##4R7|UJEOn-m6~tII)3u&q64Q#Q z?0z54`!Udw-0uKOx&Iy1UZG=NOgTb66EC(h8qKgBB_@P(uQmEc2>o&n;MKfjf z43k+0%;43H^da3x?+AZ6U*cfN1Uf?x=whO7Le*X*s%gPTE3#By!Wol$tU$jM#y?;79>uB%=5Gb_YVOzC9tHHJ4)obdd`2NI_$iG&_~eEV+dK7 zOT$ic=J$cZ=wwZU(Jf#Y#?~2^Qc{}`u2VEm+vtAWKMZVA&$ZQRQ$y>d#fAO>UV)wS zB_j*|!Pkalq`Ppkj5U;Blhn}kIx}3Nt(f>{glwB=XrmHgtk-j(FGq?V{Q)O0;Oacv z8MZ${$Y{y8KB9jN{lc}Dk*DFMvsl)HP>&5=qG4)lsM@NTn>OVklXG~s0AqiV+8MS# zvX|unbH)Zt`auJC8~O@^YN=rfOTD0(K%ZR(7DYR!L24%muSjH)bXysmXDp$1Ghy?Xkq)ZANnr8z=}$H7_O*xhwp0dV`QV`y-BnbO3`Cq>;z7Wj(Y03 z*b-whkrIG5Wn)dz%0xJ(V?<3ji_?paNFkKH2919@dYCEg4Ex&dII>ZCv4rm`es~r5 zC9{|)sa_f;U&t_l2EcY1mQXmE5X$+x_|*Fmgy#4=j0*v01otn{SJuy@s3h@aKk2=U zkXetz@*afg352K%QWx;@z77QgU!M##ZQr6J zxFmmj9auPKu&3^-fK%@<0VcAnq#Ej_RhxW=oWBvC_oYRJX}q+XCui;O%aLSnJ=9z+Wa+_%I83_FY-XdpVxGJuU6Pk}e~a8rPWmphDIR2mQc?}o z5Tm^oLNei&zaB<{ttwPG?sOm_v-jpfB^vcrKKZQd3%`v0000< KMNUMnLSTaQe!i3d delta 981 zcmZn?JHV^h8Q|y6%O%Cdz`(%k>ERLtq=i73gAGWAo$CvqsHnkMKhbNlf{}u`rJkXw zrICe+j)IYap_#sciN2wwuA#9N5SbWk{F}(AU~FdLY~*6(Wa;c|X=vzbW@+qV>1=6k z>FDBYVCLjvF}aRO2Bydnr=ndL#U_Ch8id zrkNzC7?`J;85m4F!Eg`=C z!hs^TXMC0$FO~ILy+zASh?nylyT_wLYdNJ&RMz!Sr`uKhiqn_9zgPZl=kt5!wU;Xz9*6Vr9?AUF zvxt*f_aD!Xjs?4q-5oa5lr{kwawB~G|}!*SZf^vq9o4grip@h5)ESM>XAdCWH}x%7sy;g9YA8HLoC zjGBDsJf5aF<=`5f2Me|Y->`f^4K$WnUBAAFPR117ad=?}fv+&AoUx_19irN_c^ zo6=ld?j{L_`j|9&`*%Gls_4zG-_dmX&wj`3%hweCJ?Gs2QL($ISp2WP=>*613zzbg zmOq)x+8?yVJf$eGx#wa{RP#Z`XA&mqSxj8!x87UDW>0)=*CTk~%a3`V zztG&y-@sBTIge_f%~q%HqP?e z1;;IS`pjB5ZC~ET=-~M)|FJCQlG<{Q@u5Lc&g3I97F<(~-795(HO(pI1^cGYi)I^~ zU2$p6>X3!jH-q{N1Sc>(|05n<&2qveyM6ZjoC?Ll&?c`rtJGpeUCf2=+_TLF2RBQ~ zV+(;T^VHIpF diff --git a/res/icon/specular.png b/res/icon/specular.png index 355d563cbf5cd012430ecf2a1c5138292ed392d9..97e6ef68d2613c403e348b9cbc22841cc0e94613 100644 GIT binary patch delta 1857 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 diff --git a/res/icon/sun.png b/res/icon/sun.png index c3c5292b711db96a5f8c653dbfcf6cab9b6ae80f..b13a4814799aa06b92636174009d4a753467cfd2 100644 GIT binary patch delta 1127 zcmV-t1ep7Y3)l`JiBL{Q4GJ0x0000DNk~Le0000a0000a2nGNE0O0_bn2{m3B{nT% zF*7(IH#sgbH90skG%O%8FfleSFgY(VG%YYOIxsmpFfp9qLNP%yML{+(FgQUo zGdVXhlez+5BsF1SVKzB3VJ&7iG%zhNGc;i>VPZLBEi*7NHe@q3IWRdiIg>pDf`8TU zBM$%o1AR$EK~#9!%vif_6G0T6^=th|>^L?CS}K%=l0Hx*Kav6oC}Z?~T22R=W1g?0DwPxo76yvElo^x=&APRawcpqF zL)F9@7PUuqBDS|VhHPt>K0lKkjem^ulI$nhN3yfq3~NNzkZq*|#3!=qj9zB~3gDdh zTxMGb^!bbIdkBozV;eqI>4Uxp>u1QeppD~+!M(mxgA1kl7hF&8D3u{kCNkqA)XCnF zoi1W%J|NqwiffKSdh=ZZaZX@b*SSy z*$KS^BZDil1~cQ_a9J}%?4VMe+h2`xUd~b-A1ops8zN$R8&r;z^B3I=_XL z8wOf=PQNa(w)Et*i4tiB*ZW8P37|{wQY~gO=TUikjiJLIu%H31_J4vV&oM9n0!U1% znQU>pt`jKp0gFAhSeAXnOki?vz;40Ba3Qp$Srm~2SnRK8e5Or{}G z0U69j?lFNNAjvF;3Ix|gmP16kD8V(z7|W8RNg85S!Z{K`hjycJONJ1Y5uFiCv2uii zq<|TexE*;8fCZWXo0*a1OtZ8#FTGs|=#cCy*;AH%hQkF`2!Gw6uGuQecb+4|3WbQ6XVZV@l!!f(&B!47%)8ZGM1T3vB$&Ydk z3ewvERLtq=i73gAGWAo$CvqsJLC#R5#hc$WX!DQqR!T z#M01EN5ROz&{*HlK;Otx*U-?)#N5izY~$ZVMg;>?XCqT*M^i^b6GuZsR}&{^V>35n zS2tHD7b7PZQ-jHMOfoP{a&cz`|^@njK#Z){soWvEd=!5wd`i@?6wbL|HnMtSHPuU#T9q^6}i$jn>T;m zm@|FG6HoK^XKai=pSx`SHR|5+{W&F8KRnf2cDiV`+}1Ye({RrbYR`x}%Q7K9LHmoU zkmM1YLZE-@sxhr)|N1NE$bMF% z?|eDw($h8{xSX0~?iF(AvE+w}b5FX|I4>4Q?)}TEDmwouL$k0RoBGptL4u3I7X&A( zhab4iD*TS2T0~-&(oJ6OIj94o6)!%*gRA%=`ZUKZ0CQ*SSc%7{h3C9?lj5wEhn{)zMt^8u}b{TUHvz{4U7y0F5Q*tf)ne3>4?G8)z4*}Q$iB}2BP^j diff --git a/res/icon/textures.png b/res/icon/textures.png index 807d06270b40715944f60bb2a47a5d68fc0249d3..83a95020fa4c6d65ee88a0f6633ef18be41b3355 100644 GIT binary patch delta 486 zcmdnXd52T6Gr-TCmrII^fq{Y7)59eQNGpIa2OE$quB!SnQE|JfnQpS7v4w)UrJkXw zrG=4+j)IYap_#scrM{twu7RPIfu)s!;l{s-j0&a(PHv`dCML#Cjz)%tuI82&PR1@4 z<`%9dCaz9yPDYdKm}FpzjBzU3#T2h%X>4F_VP=}9YiwYeteco>VyJ6rkZ7!HoM@Jq zW^Q2!bb#SxTjr+v4U06K85kHDJzX3_DsH_A59Df55ZQ7^xuosi-?t)j7tLmsQ{>p? zC{e;xZ`JVs>8^l==8aUZBrN&TCNApU6#hyC-*_jp@7YWq2A+g$2PM^hYd6; z<~{FHTbAh*CuuSJ(rJ@|Z|#o~9as{k7R%~)Ii37z(dXvtF@J}@q}*P6Ew(u|3v#2i zePiY^y*8?;amXq7#Q4|dV(HvK;oAGZzc#K<{qdt=HUE{;+@+mAPJOt}_~(A(k*n;h zRw>>7n|Rjx*o_sP%QBTdH$F&}m*{GI@@@O$?F`HeCzsucXW9La85ng8p00i_>zopr E00Tm`4FCWD delta 421 zcmcb^xtCM1Gr-TCmrII^fq{Y7)59eQNDF{42OE%-|NK93qT+T{Q{7|(BSQsqOFctV z6H7xw9R(u;Lt}kI1AQY)T|+}F6LTv=vyFcf85N974c%NV94!o-%*_oAT}>P;N1IY{gro)j?YxB6R+!SR-1XUX6BQ#-~XL? z@p@NQf1AbuOLv*$0@a3x4O|3RCdFQFeQYr)_jX%qz&M!PJWj8Y?B>({b$o;m4lmfqV9h`5-1|r|JdVArQAmo-+!5GUTVMV zHt9IeTDD{M$yBy*HKXQLt5z)EmRmd3r!8^E^n?DBYwc4b7intO_M~VJ^mZ{m|Nm8A-15(9_iDfUy}z>l zx^40EzyI%+pWk`CD0aKfsSP=|`&@1m&e@-?bKs6w!SN?Y>wh0mtYE9Z|A5)1$s~g} zX^GN-ImH(|igFVspXA=mJ1Mb%OX$JT_fL<8C5V?yyu+{~<;cz#jN2Ee_B2*%KI>K7 zAYAbDQy}v>hrO#BUEN}8qPw5SI|yxQ&fCCn@8q1Q4a=ig^^BK2l1`9G@!Tgh$0PXO z3x@Djp&#<6^;|zFp{%FGTy;?TWc~Nmc~1KU9tFETOG#N;vH0?oM$ey~ES3h3jNi}d zdF>?UfSDC!qa(8Cf+I3{Kg>e zozV1TUP`{kPDVvL@1jRbWTu>-B=z8k>&iYORmmR~d{U=fHo&WF?ld{}lhi!f>o5BTOGo_sU-59~uv7Wh* zTa)d8$!X?~x(a2j$Jhed7G#$(JzCxpq;zrCM@fe%X*FyDOC{$savjNFJO2Cs|Nok& d&M`2uG1N`a7dqGCGY#lV22WQ%mvv4FO#uJag%tn* diff --git a/res/icon/vertex-colors.png b/res/icon/vertex-colors.png index 25686b1fefe2f9aa7ca25e7c58409316b479b9b2..6490d7e0912b6eb5cf3208026e41012253853b41 100644 GIT binary patch delta 1488 zcmV;>1uy!e4ZaZ}iBL{Q4GJ0x0000DNk~Le0000W0000W2nGNE0CReJ^pPRAB{nT% zF*7(IH#sgbH90skG%O%8FfleSFgY(VG%YYOIxsmpFfp9qIX6W!LpV1wL_$V3 zMKeV?lez+5BsnuMH#jymW-T)?HDfJdWi&A@IWS=}Ei+*@VP-ctF)%SUF_S$5f`9ok z$nyXI1mj6WK~#9!|Fwr44D(UmoLVtUgY>hEcG`;7n(U)qRJSXqnT=pK*_|??k-8J7L zTj7$EeS0-{Le^#s&wEd`HHBC)zPb{*BMflP)!SL0)W_5BS$>x1>(5K~$Yj7zULghy z1%MeTvNmH8StsI>mhobGO|l-NKgPmzew6r0PEpJ-Q=Yp3GL9A282@Fq27kc7B?z)m zjEraa``juQA7!9kZT^v-5Qi-7P2QlB`h+a8@pAKL8aGo$4t6H)k`_XyH1Y23_D?iE zE4fB?cgKE*td(wg^Udf{y0JD^6bzm;OBsAX7ulL9W^052+gBGOQ-qNx@Z5_8Zq$!S z4vYnY1+bnT(WM9EO{^w)6MyhxVPam}26J3+EbRMHJP6t}FJ&P6&W56*9~n6eaP^Pz zaTuumIgL0sLyp|&FP`E~12aXwRePV1HlTLNR_-5tL*vVlhV{1}&TkUp3eyDn@7)Q1 z#KNKOz`rf-f+3BDSaQ~`ToKvsQgO@<9`VQ0;&$}BsEa{ z8;Swr$czbDEVShAVeB8Lp?MzMVP1J+^i=A6LDOa6%}H# zV?@EyUYZX)#huv@SilT5*tt_z)dV(;8nFNO6=1duB~Zg;3zVe55Cxj#vEXXj*t<~fd!(`;^zWWs*E=h@Y) zpZSuz&rEzt$`w364`5|p&=0)vLPd|&MWSQ#hdPiM4a*|yO}WT$0WafDJ4aWiY5X5+ z5%cN`4MFol*MC4%G+R}y?YP~v`kBy#>vo4ksk4iT04J5%^_*X(30dIp$N%v9X9rk& za{P#_+tg9*8us-M=(Q%BMFy;G2j3#KOBXUeUeZ3L@wSFU$xa^M`3{{`#X_&A&+dOj z@npFc7%7!*?ZEH1^)b8R^ID%5FDr|{(G^|O0Etgx`m<$Zo^Xwk4C|8{M z3OeN>285WqK}Z{=vZ@U)Jlt6~4$<8nRA=u)-Q0$Vy}g+&8gFUW)IzoThf3>cpV`6l zTfbv(SASxU%FOe1bXLA#gJB@DI@iMNnDg6Vup{OLJ?2G5&KQt^g)j=agJRH&G~8uJ zmT^cwa-fMG{njBQ%yLxEsES+YYw!omLdO&OI_CR53wJK`3MzE3XET|{z-c{G1_0qr4yYaSQU(A300{s|MNUMnLSTZk*RvG> delta 911 zcmV;A191Gl5u*(uiBL{Q4GJ0x0000DNk~Le0000G0000G2nGNE03Y-JVUZ!XB{eN$ zFfuV9H#sgbH8eRfF)Sc5FflVPF)%MOIV~|UIy5&rF*dRNVF4gALohcvHaJ2sLN_-t zF-0^%K{zr+Fhw*qG(|W9nK|we&MKDD)H8e#y zLNP-@lez+5lO6&llRpE1e<)e08~^|U$Vo&&RCwB?lU+{~F%*W+>2%te*)NubWr3hX zRslt0jKqYXG4U6W_ydeL#^2+;(QAKzUPxl}N@5hv#t))q1q1>@_Isu??Hs!VUGMEh zlarqJ>D#w05)u5*;Qi_JPOg`?prOg`Mg@S1b$_;;0?nTZKaJZuf7}q~3rKkJZ1ULy z!1`BbmI1-pstJw(3J^kT#I?;`0OO~Olm)~9v9{~R*2W5KOTT24_T~RLXpFVLqyTBA zo}W{;lV)0Pgw^fTa*NuY@!G}C>k^@}QS+`!K%75kK3bXc7vL%ACt*^>C^pBtcM}GQ zj=0e&fkW7KQYPY_e@DoyV`b956Cq9jm2bsaS0lw!N3XT8; z0)xg2@_wps# z<5X<_eC~I>#y%|EX(S~x5|2J;B7 zy32*zlrkF1f3O7d(Ayg?9>^(dPBvNMyV&lOxgUh~`h<1S8#tK#vi~t?^Q7sWzR`kG zI=-%-YRahnY3FX&>B8aXE6-Fx2E4R=OZpOn;6hEQF8>Rd*k+FCF^YyIGV)jy z#z`-5G|NKR^7D;w$dwc!vroYlIhM_K1`mm`(Ya7&65F*Y9qIYC7?MnpL^H8MFe zMnyI-lePk1Bw=M?G-YCAVJ$T>F*q$XV>DtdI5;zAEiz^{H)1k0WMO7EHj_C6et&m1 z$87)r1lvhOK~#9!?3mwATV)u>pVOWKt+b^r8fbA511f@sB^rm2VIo(sWZC_;KVUao z7PCwBdRewz?P|-Cu`8C{?T1+=i3XhF#R#(SYoM?e(b3=Z2kmK(&-d+n;0P-PHD(&# zdeL!MGTYq>gbib~$gA>kCp$hDkCpCMroVX)&+so8lB@SAsBJBSpN*_?ZCH zDEoU6^ql#X2{Fr}uP6|g{gbdVw+ybgNn;I6Gtbm<9Y5YqCr|pQyW7V7y+vuB?TwD+>BfyP-MsmP`%^sn6En&D!bGH3OQQ5m zsu@4i%Y4QR=|_()>T`3N{(nz3?1eqCcgaStv{`NFfQJmv)H81}hnOz9c(IqxoN;aq zI-?bfMM|gB6b^?ekw}O!m&-*ypN||42U#p-G74VimoF#j?AdR)_lWtPnPpbtDeIR+ zugE}sm+59crVAH7;SbdABqkDx(CqB2Fy7qU%;8lpge@*EQcX>bK!17B=P}mRRZUK3 zfF3;1WvJvtkRvtlNE7oZ^EvaCe(YGRlIN4jq&_w_rmwHBZ=1sy=CGz>gDu!B*@lKD ziwxpc3Isl&+qVzXty}I&af)MtN2;p$r0?J7DDaoyXN+MEYbrL_f=$>i-DWR5)WdvH zoS8}SQS2%B>jadx9Uf2_R!+!>B!KQs+U>F*p1=qfb zAKqqqaNU!AeTP=ATyY~7>~^~V&+ryN!-i8+Q=G6nOL1gsd|aHM$EmHYjcRL4-y0qQ zn9F1`0z5aIIu&<5e28oR9W%=10u%K&pyFb~{QH4$5QegHHgyC94RGu?2 z3}emG(h|Ql=6^*Rz!=tJ9$tY6!{B8bA962a^rw&wncu<0Ero zG{4JG-}t1c4?&`@@1y0@rvteI2dofbh%pEJ){Ja;)PEef5QM)~OG^d+rAr~#@Gv~` zqXc5YvTmyQa(!P{)psKO{q5l+M||mn2W<+Af(Wx9;|Ai0%!0&Gvlz$Y$9dnKJ7Mp= zdruTio93kx%e7yzL)f-4Bu3gZ9(002ovPDHLkU;%;z^rVRZ delta 678 zcmZ1{IGJ0qGr-TCmrII^fq{Y7)59eQNDF~52OE$KJJ%OJQE|Jfscy1?k)eXQrJkXw ziKU^Tj)IYap|QT9fxeNYuA!loiMf@b*~Y)Ij0zS81}3Jimc|C=W`>4_t|k`7=1y*= zh8D(7KpjRF#*?dnQEAp zW|m@XVPHDhlDV$FV1-670|Vm$PZ!6Kid$!bZM~TtMQWbc9_dbC;dB#_j+Q=p&+8AT zmeLHT4WSzXbXfm4B_t~TkX8!Y)|rtk>|~|bsrH~%CHq~`_nOU?&Os3~4>dOK-2S=k zZuxFD4Nj{b-7vuZWBnmHN2;P@`xT?VN+R2Ko%4@jl<6F;18GmQnE3r*2 zFlxtXZf;9E%fLCO4fpRg)V`{2k`>X+ey@)Iqr!@K3*q4Q+qrB_3JMy7BiA5hK_4hl=F-dW^HhtM*x-d}s;Kjfd)vC;nw`P7?Gw-P^`-sT=fCao64lQSlxN$nmzCRf?hKD* zpYz?k={DytTYD}In&P3tQTPAz|1X3TFTS From dea625f66e605ac8b6ec8726af5c2b83686892f0 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Thu, 18 May 2017 20:38:39 -0400 Subject: [PATCH 028/152] Moved and removed unused icons --- res/icon/load-view.png | Bin 1247 -> 0 bytes res/icon/save-view.png | Bin 1261 -> 0 bytes res/{icon => img}/havok_logo.png | Bin res/{icon => img}/qhull_cone.gif | Bin res/nifskope.qrc | 6 ++---- 5 files changed, 2 insertions(+), 4 deletions(-) delete mode 100644 res/icon/load-view.png delete mode 100644 res/icon/save-view.png rename res/{icon => img}/havok_logo.png (100%) rename res/{icon => img}/qhull_cone.gif (100%) diff --git a/res/icon/load-view.png b/res/icon/load-view.png deleted file mode 100644 index 5e3888e3cd5f1c34e89cd38f5e8491ca2475c365..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1247 zcmbVMO>Em#9CzC(jDAcNsG{A4C%1e|)b?`{$FbKmN$q5f)O1l6)*u0@W4|_T>yO!P zlNQvKu0T7`iV%V$J@kMOLK=q&30fGmf}xEgs?d;N2*C|;s8pd!}Ti zkt19dW^Dm#sB@F5ppo%@&{q|)0t0yps2WqID^_FVfnE`xeKA9V1Bf>n zBM+S_W^+KY90WL;qf{7V0WZ)j7ZzBy7X%?2Vpzxo1&U?GFfXzZP+KIn=4fMLUQX9+ z;aiL>d!8*aOr=txDr-vaJb~byY*mP@&!YN~_ip{VzWDLWP>j3R~dGxOv$D-ZBtc{pFa;<4c#q}8L zu9I=@9&E@5VsFIrj*g3>Ov#oyZ6MRj$T1Rsp*39-`LrCB(y}B&7)ga$b|4%|B~u~1 zWL|=TRM&AF7fMM^lEX>JM6f|)H=%z#>VAAVnjd81 zp>gnZEdIJQLl6zE897;~{#e?-e?Q*D5#98a&aN)yb_LMwEsN18708T!owHllPh4I7 z<=NG5);1FRkKF?8FEK(O(|En(?IgI-L+>tqwcN3MIr-|&!X8iW_HJ%&_H3V`ci-&q z?=P&(KiOL%6VIRAI=is&{fKJdZm&*{c96AdN0@zh>mi%Z;Hy^{jc-*2_8 zEH97le0r(7^UBWPLd%CYh@aNB6L;S*4NdEjhK5?PyqI{r!2AHoj{T-{@pm3=+4_Ag z^5xB|9nW;W+xuMOxZ3apjQ#P-3n#Yj$blK^lkM9R6BFMyUu*hn3d}eB<~Fit!|!h_ reRuw$dp+eB*XI|t^@}$H?W4qduWh{ibNZvW|I5jwhUJe3Mi>79CVZDQ diff --git a/res/icon/save-view.png b/res/icon/save-view.png deleted file mode 100644 index 9e3793dc8110fc8f839c567399ba4748d022ad0d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1261 zcmbVMTWs4@7lf|QVjHZPR=a@ty{?ZkFT zTEVn&smdnMD#7+rX=6{*gec=_%hyNo^nfds+q%=!G zAZy5zsDNZ`w)#05APAqX7fWs__XMw6MnrC4B6XvJ*#t2#Sg*)x8M#0~le(ECuYGWy z1iF?aN7)?ARRlDp56#(V-`sFfohz%HMh-p-2I@REU?5ipbz|Ce_SWAi+gwHpbEP9>734O3{#FxG+ugu>?=YL35B;&DJLP zf|zM);a!rPa@`6~QMFnvQj10`dy=9#j-wz$F-#aE!p^Me%Js16bhj*s$Wd**;_8+O z8Wv^6nsJjP_H^wEMkSY9H*7l1K;e*4b-6;(5l9(E<6f(1$1R|L-PjQA6lW`lDj>(2 zu~nRpiS8B|$L`KT4IuW0f6~@*QsimTQfCZgx>+$v;x7?R*Z4$6>=!bkAVL_IVll~^erg^75arllB{$Usqw#|1W)W;ls$akHl5%BG51x;oZv za+y1FdBH}qYuQE1nr>A)p@x~W+;r>~Knwabdg)}KRK#f=B4(mKLL*jTPt zKi;hO4>EDn*tk1|ayP-{l$4@+5J@)3v&tG^N OKcK8MEWVQ-fAJrq>6VB9 diff --git a/res/icon/havok_logo.png b/res/img/havok_logo.png similarity index 100% rename from res/icon/havok_logo.png rename to res/img/havok_logo.png diff --git a/res/icon/qhull_cone.gif b/res/img/qhull_cone.gif similarity index 100% rename from res/icon/qhull_cone.gif rename to res/img/qhull_cone.gif diff --git a/res/nifskope.qrc b/res/nifskope.qrc index a17a4199b..e4153cfe6 100644 --- a/res/nifskope.qrc +++ b/res/nifskope.qrc @@ -7,8 +7,8 @@ icon/img_update.png icon/img_link.png icon/img_flag.png - icon/havok_logo.png - icon/qhull_cone.gif + img/havok_logo.png + img/qhull_cone.gif icon/handle.png icon/collapse.png icon/expand.png @@ -26,8 +26,6 @@ icon/collision-ball.png icon/node.png icon/constraint.png - icon/save-view.png - icon/load-view.png icon/view-active.png icon/screenshot.png icon/save.png From 57d1b377dd916009ea07716b2f7f5259bfec0496 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Wed, 17 May 2017 05:22:08 -0400 Subject: [PATCH 029/152] Remove editability of Name column This was only useful for dangerous block renames and really impedes workflow (misclicks and inability to double click expand trees). Block renames do not break files in very few circumstances and should instead be hidden under a menu. --- src/basemodel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/basemodel.cpp b/src/basemodel.cpp index 0259ab828..dabab34d2 100644 --- a/src/basemodel.cpp +++ b/src/basemodel.cpp @@ -605,8 +605,8 @@ Qt::ItemFlags BaseModel::flags( const QModelIndex & index ) const switch ( index.column() ) { case TypeCol: - return flags; case NameCol: + return flags; case ValueCol: if ( condExpr ) return flags | Qt::ItemIsEditable; From 1501e7ea2c297466ce5896b3609fd40a02cb751b Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sat, 10 Jun 2017 09:28:27 -0400 Subject: [PATCH 030/152] [Build] Fix cross-platform make target issues --- NifSkope_targets.pri | 54 +++++++++++++++++++-------------------- build/doxygen/Doxyfile.in | 26 +++++++++---------- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/NifSkope_targets.pri b/NifSkope_targets.pri index 61c8e9250..05324c26b 100644 --- a/NifSkope_targets.pri +++ b/NifSkope_targets.pri @@ -15,8 +15,7 @@ QMAKE_LUPDATE = $$[QT_INSTALL_BINS]/lupdate$${EXE} exists($$QMAKE_LUPDATE) { # Make target for Updating .ts updatets.target = updatets - updatets.commands += cd $${_PRO_FILE_PWD_} $$nt - updatets.commands += $$[QT_INSTALL_BINS]/lupdate $${_PRO_FILE_} $$nt + updatets.commands += cd $${_PRO_FILE_PWD_} && $$[QT_INSTALL_BINS]/lupdate $${_PRO_FILE_} $$nt updatets.CONFIG += no_check_exist no_link no_clean QMAKE_EXTRA_TARGETS += updatets @@ -62,15 +61,15 @@ outdoc = $$syspath($${DESTDIR}/doc) # COMMANDS docs.commands += $$sprintf($$QMAKE_MKDIR_CMD, $${outdoc}) $$nt -docs.commands += cd $${docsys} $$nt # cd ./build/docsys -docs.commands += python nifxml_doc.py $$nt # invoke python +docs.commands += cd $${docsys} # cd ./build/docsys +docs.commands += && python nifxml_doc.py # invoke python # Move *.html files out of ./build/docsys/doc -win32:docs.commands += move /Y $${indoc}*.html $${outdoc} $$nt -else:docs.commands += mv -f $${indoc}*.html $${outdoc} $$nt +win32:docs.commands += && move /Y $${indoc}*.html $${outdoc} +else:docs.commands += && mv -f $${indoc}*.html $${outdoc} # Copy CSS and ICO -docs.commands += $${QMAKE_COPY} $${indoc}*.* $${outdoc} $$nt +docs.commands += && $${QMAKE_COPY} $${indoc}*.* $${outdoc} # Clean up .pyc files so submodule doesn't become "dirty" -docs.commands += $${QMAKE_DEL_FILE} *.pyc $$nt +docs.commands += && $${QMAKE_DEL_FILE} *.pyc $$nt docs.CONFIG += recursive @@ -104,28 +103,29 @@ doxyfile = $$syspath($${OUT_PWD}/Doxyfile) doxyfilein = $$syspath($${PWD}/build/doxygen/Doxyfile.in) # Paths -qhgen = $$syspath($$[QT_INSTALL_BINS]/qhelpgenerator$${EXE}) -dot = $$syspath(C:/Program Files (x86)/Graphviz2.37/bin) # TODO +qhgen = $$[QT_INSTALL_BINS]/qhelpgenerator$${EXE} +win32:dot = C:/Program Files (x86)/Graphviz2.38/bin +unix:dot = $$system(which dot 2>/dev/null) _7z = $$get7z() # Doxyfile.in Replacements -INPUT = $$re_escape($$syspath($${PWD}/src)) -OUTPUT = $$re_escape($$syspath($${OUT_PWD}/apidocs)) -ROOT = $$re_escape($$syspath($${PWD})) +INPUT = $$re_escape($${PWD}/src) +OUTPUT = $$re_escape($${OUT_PWD}/apidocs) +ROOT = $$re_escape($${PWD}) GENERATE_QHP = NO exists($$qhgen):GENERATE_QHP = YES HAVE_DOT = NO -DOT_PATH = "" +DOT_PATH = " " # Using space because sed on Windows errors on s%@DOT_PATH@%%g for some reason exists($$dot) { HAVE_DOT = YES DOT_PATH = $$re_escape($${dot}) } -TAGS = $$syspath($${PWD}/build/doxygen/tags) -BINS = $$re_escape($$syspath($$[QT_INSTALL_BINS])) +TAGS = $${PWD}/build/doxygen/tags +BINS = $$re_escape($$[QT_INSTALL_BINS]) # Find `sed` command SED = $$getSed() @@ -135,19 +135,19 @@ SED = $$getSed() !isEmpty(_7z) { win32:doxygen.commands += $${_7z} x $${TAGS}$${QMAKE_DIR_SEP}tags.zip \"-o$${TAGS}\" -aoa $$nt - unix:doxygen.commands += $${_7z} $${TAGS}$${QMAKE_DIR_SEP}tags.zip -o -d $${TAGS} $$nt + unix:doxygen.commands += $${_7z} -o $${TAGS}$${QMAKE_DIR_SEP}tags.zip -d $${TAGS} $$nt } -doxygen.commands += $${SED} -e \"s/@VERSION@/$$getVersion()/g;\ - s/@REVISION@/$$getRevision()/g;\ - s/@OUTPUT@/$${OUTPUT}/g;\ - s/@INPUT@/$${INPUT}/g;\ - s/@PWD@/$${ROOT}/g;\ - s/@QT_VER@/$$QtHex()/g;\ - s/@GENERATE_QHP@/$${GENERATE_QHP}/g;\ - s/@HAVE_DOT@/$${HAVE_DOT}/g;\ - s/@DOT_PATH@/$${DOT_PATH}/g;\ - s/@QT_INSTALL_BINS@/$${BINS}/g\" \ +doxygen.commands += $${SED} -e \"s%@VERSION@%$$getVersion()%g;\ + s%@REVISION@%$$getRevision()%g;\ + s%@OUTPUT@%$${OUTPUT}%g;\ + s%@INPUT@%$${INPUT}%g;\ + s%@PWD@%$${ROOT}%g;\ + s%@QT_VER@%$$QtHex()%g;\ + s%@GENERATE_QHP@%$${GENERATE_QHP}%g;\ + s%@HAVE_DOT@%$${HAVE_DOT}%g;\ + s%@DOT_PATH@%$${DOT_PATH}%g;\ + s%@QT_INSTALL_BINS@%$${BINS}%g\" \ $${doxyfilein} > $${doxyfile} $$nt # Run Doxygen diff --git a/build/doxygen/Doxyfile.in b/build/doxygen/Doxyfile.in index 974d67bc7..8ca900d52 100644 --- a/build/doxygen/Doxyfile.in +++ b/build/doxygen/Doxyfile.in @@ -751,9 +751,9 @@ WARN_LOGFILE = # Note: If this tag is empty the current directory is searched. INPUT = "@INPUT@" \ - "@PWD@\lib\fsengine" \ - "@PWD@\lib\NvTriStrip" \ - "@PWD@\DOXYGEN.md" + "@PWD@/lib/fsengine" \ + "@PWD@/lib/NvTriStrip" \ + "@PWD@/DOXYGEN.md" # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses @@ -905,7 +905,7 @@ FILTER_SOURCE_PATTERNS = # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the doxygen output. -USE_MDFILE_AS_MAINPAGE = "@PWD@\DOXYGEN.md" +USE_MDFILE_AS_MAINPAGE = "@PWD@/DOXYGEN.md" #--------------------------------------------------------------------------- # Configuration options related to source browsing @@ -1113,7 +1113,7 @@ HTML_STYLESHEET = # see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_EXTRA_STYLESHEET = "@PWD@\build\doxygen\extra.css" +HTML_EXTRA_STYLESHEET = "@PWD@/build/doxygen/extra.css" # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note @@ -1349,7 +1349,7 @@ QHP_SECT_FILTER_ATTRS = # generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. -QHG_LOCATION = @QT_INSTALL_BINS@\qhelpgenerator.exe +QHG_LOCATION = @QT_INSTALL_BINS@/qhelpgenerator # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be # generated, together with the HTML files, they form an Eclipse help plugin. To @@ -2027,13 +2027,13 @@ SKIP_FUNCTION_MACROS = YES # the path). If a tag file is not located in the directory in which doxygen is # run, you must also specify the path to the tagfile here. -TAGFILES = "@PWD@\build\doxygen\tags\qtconcurrent.tags=http://qt-project.org/doc/qt-5/" \ - "@PWD@\build\doxygen\tags\qtcore.tags=http://qt-project.org/doc/qt-5/" \ - "@PWD@\build\doxygen\tags\qtgui.tags=http://qt-project.org/doc/qt-5/" \ - "@PWD@\build\doxygen\tags\qtopengl.tags=http://qt-project.org/doc/qt-5/" \ - "@PWD@\build\doxygen\tags\qtnetwork.tags=http://qt-project.org/doc/qt-5/" \ - "@PWD@\build\doxygen\tags\qtwidgets.tags=http://qt-project.org/doc/qt-5/" \ - "@PWD@\build\doxygen\tags\qtxml.tags=http://qt-project.org/doc/qt-5/" +TAGFILES = "@PWD@/build/doxygen/tags/qtconcurrent.tags=http://qt-project.org/doc/qt-5/" \ + "@PWD@/build/doxygen/tags/qtcore.tags=http://qt-project.org/doc/qt-5/" \ + "@PWD@/build/doxygen/tags/qtgui.tags=http://qt-project.org/doc/qt-5/" \ + "@PWD@/build/doxygen/tags/qtopengl.tags=http://qt-project.org/doc/qt-5/" \ + "@PWD@/build/doxygen/tags/qtnetwork.tags=http://qt-project.org/doc/qt-5/" \ + "@PWD@/build/doxygen/tags/qtwidgets.tags=http://qt-project.org/doc/qt-5/" \ + "@PWD@/build/doxygen/tags/qtxml.tags=http://qt-project.org/doc/qt-5/" # When a file name is specified after GENERATE_TAGFILE, doxygen will create a # tag file that is based on the input files it reads. See section "Linking to From 6fcdb9917a4c89a95989e8c74d2682005c20cbfa Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sun, 18 Jun 2017 18:33:43 -0400 Subject: [PATCH 031/152] [UI] Default Quaternion rotation to YPR YPR is simply more natural to edit and it doesn't need to default to Axis. --- src/widgets/valueedit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widgets/valueedit.cpp b/src/widgets/valueedit.cpp index eb33f2992..3a9c0e691 100644 --- a/src/widgets/valueedit.cpp +++ b/src/widgets/valueedit.cpp @@ -823,7 +823,7 @@ void RotationEdit::setQuat( const Quat & q ) setting = true; if ( mode == mAuto ) { - mode = mAxis; + mode = mEuler; setupMode(); } From c2039e1826be4a7df94d93fc7202d11cc1a5d8e2 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sun, 18 Jun 2017 18:35:52 -0400 Subject: [PATCH 032/152] [Spell] spFixInvalidNames fixes Needs to ignore EditorMarker strings as well as BSValueNode blocks which specify duplicate strings all the time intentionally and changing either of these will break the NIF. --- src/spells/sanitize.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/spells/sanitize.cpp b/src/spells/sanitize.cpp index ecfc4dabc..34aa42d93 100644 --- a/src/spells/sanitize.cpp +++ b/src/spells/sanitize.cpp @@ -404,6 +404,10 @@ class spFixInvalidNames final : public Spell auto nameIdx = nif->get( iBlock, "Name" ); auto nameString = nif->get( iBlock, "Name" ); + // Ignore Editor Markers and AddOnNodes + if ( nameString.startsWith( "EditorMarker" ) || nif->inherits( iBlock, "BSValueNode" ) ) + continue; + QModelIndex iBlockParent = nif->getBlock( nif->getParent( i ) ); auto parentNameString = nif->get( iBlockParent, "Name" ); if ( !iBlockParent.isValid() ) { From 8d0a697b6a3980da65ce918380435e698728518b Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sun, 18 Jun 2017 18:37:16 -0400 Subject: [PATCH 033/152] [UI] Fix broken right click behavior The change in 57d1b37 broke context menus in the Name column and also the viewport. --- src/nifskope_ui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nifskope_ui.cpp b/src/nifskope_ui.cpp index 4bf25e0e8..ef7c3ecca 100644 --- a/src/nifskope_ui.cpp +++ b/src/nifskope_ui.cpp @@ -1229,7 +1229,7 @@ void NifSkope::contextMenu( const QPoint & pos ) SpellBook contextBook( nif, idx, this, SLOT( select( const QModelIndex & ) ) ); - if ( !idx.isValid() || nif->flags( idx ) & Qt::ItemIsEditable ) + if ( !idx.isValid() || nif->flags( idx ) & (Qt::ItemIsEnabled | Qt::ItemIsSelectable) ) contextBook.exec( p ); } From bb416519d41c3eb7b83c17380c564882463a4067 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Thu, 22 Jun 2017 08:42:38 -0400 Subject: [PATCH 034/152] Static Analysis fixes Mostly default init and local variable name overrides, with a few actual bugs fixed too. --- lib/fsengine/bsa.cpp | 2 +- lib/fsengine/bsa.h | 4 +- src/basemodel.cpp | 4 +- src/basemodel.h | 2 +- src/gl/bsshape.cpp | 12 ++- src/gl/bsshape.h | 4 +- src/gl/controllers.cpp | 58 ++++++------- src/gl/controllers.h | 62 +++++++------- src/gl/glcontroller.cpp | 3 +- src/gl/glcontroller.h | 24 +++--- src/gl/glmesh.h | 4 +- src/gl/glnode.cpp | 8 +- src/gl/glnode.h | 2 +- src/gl/glparticles.h | 6 +- src/gl/glproperty.cpp | 16 ++-- src/gl/glproperty.h | 69 ++++++++------- src/gl/glscene.h | 2 +- src/gl/gltex.cpp | 6 +- src/gl/gltexloaders.cpp | 13 +-- src/gl/gltexloaders.h | 4 +- src/gl/gltools.cpp | 2 - src/gl/gltools.h | 7 +- src/gl/renderer.cpp | 15 ++-- src/glview.cpp | 53 ++++++------ src/glview.h | 2 +- src/importex/3ds.cpp | 11 ++- src/importex/obj.cpp | 4 +- src/kfmxml.cpp | 12 ++- src/material.h | 168 ++++++++++++++++++------------------- src/nifdelegate.cpp | 3 +- src/nifmodel.cpp | 21 +++-- src/nifmodel.h | 2 +- src/nifskope.cpp | 54 ++++++------ src/nifskope.h | 2 +- src/nifskope_ui.cpp | 18 ++-- src/niftypes.cpp | 2 +- src/niftypes.h | 8 +- src/nifvalue.h | 20 ++--- src/nifxml.cpp | 38 ++++----- src/spells/animation.cpp | 2 +- src/spells/flags.cpp | 4 +- src/spells/mesh.cpp | 4 +- src/spells/moppcode.cpp | 2 +- src/spells/skeleton.cpp | 2 +- src/spells/texture.cpp | 4 +- src/widgets/colorwheel.cpp | 20 ++--- src/widgets/colorwheel.h | 6 +- src/widgets/fileselect.cpp | 4 +- src/widgets/floatedit.cpp | 2 +- src/widgets/nifeditors.cpp | 100 +++++++++++----------- src/widgets/nifeditors.h | 2 +- src/widgets/nifview.cpp | 2 + src/widgets/uvedit.cpp | 50 +++++------ src/widgets/uvedit.h | 6 +- src/widgets/xmlcheck.cpp | 12 +-- src/widgets/xmlcheck.h | 4 +- 56 files changed, 480 insertions(+), 493 deletions(-) diff --git a/lib/fsengine/bsa.cpp b/lib/fsengine/bsa.cpp index 69a90431b..04d7b2321 100644 --- a/lib/fsengine/bsa.cpp +++ b/lib/fsengine/bsa.cpp @@ -418,7 +418,7 @@ bool BSA::open() else throw QString( "file magic" ); } - catch ( QString e ) + catch ( QString & e ) { status = e; return false; diff --git a/lib/fsengine/bsa.h b/lib/fsengine/bsa.h index 7f220263f..16b4d8312 100644 --- a/lib/fsengine/bsa.h +++ b/lib/fsengine/bsa.h @@ -301,7 +301,7 @@ class BSA final : public FSArchiveFile //! Whether the file is compressed inside the BSA bool compressed() const; - F4Tex tex; + F4Tex tex = {}; }; //! A folder inside a BSA @@ -340,7 +340,7 @@ class BSA final : public FSArchiveFile //! File info for the %BSA QFileInfo bsaInfo; - quint32 version; + quint32 version = 0; //! Mutual exclusion handler QMutex bsaMutex; diff --git a/src/basemodel.cpp b/src/basemodel.cpp index dabab34d2..1330a4ae7 100644 --- a/src/basemodel.cpp +++ b/src/basemodel.cpp @@ -641,9 +641,9 @@ bool BaseModel::loadFromFile( const QString & file ) return false; } -bool BaseModel::saveToFile( const QString & filename ) const +bool BaseModel::saveToFile( const QString & str ) const { - QFile f( filename ); + QFile f( str ); return f.open( QIODevice::WriteOnly ) && save( f ); } diff --git a/src/basemodel.h b/src/basemodel.h index cb24736bd..b051a0f93 100644 --- a/src/basemodel.h +++ b/src/basemodel.h @@ -104,7 +104,7 @@ class BaseModel : public QAbstractItemModel //! Load from file. bool loadFromFile( const QString & filename ); //! Save to file. - bool saveToFile( const QString & filename ) const; + bool saveToFile( const QString & str ) const; /*! If the model was loaded from a file then getFolder returns the folder. * diff --git a/src/gl/bsshape.cpp b/src/gl/bsshape.cpp index 73d943b8f..1abc942e0 100644 --- a/src/gl/bsshape.cpp +++ b/src/gl/bsshape.cpp @@ -518,8 +518,8 @@ void BSShape::drawSelection() const glDisable( GL_CULL_FACE ); // TODO: User Settings - int lineWidth = 1.5; - int pointSize = 5.0; + GLfloat lineWidth = 1.5; + GLfloat pointSize = 5.0; glLineWidth( lineWidth ); glPointSize( pointSize ); @@ -711,8 +711,6 @@ void BSShape::drawSelection() const } s = sidx.row(); - auto nif = static_cast(sidx.model()); - auto off = sidx.sibling( s - 1, 2 ).data().toInt() / 3; auto cnt = sidx.sibling( s, 2 ).data().toInt(); @@ -771,11 +769,11 @@ void BSShape::drawSelection() const if ( n == "NiSkinData" || n == "BSSkin::BoneData" ) { // Get shape block if ( nif->getBlock( nif->getParent( nif->getParent( blk ) ) ) == iBlock ) { - auto bones = nif->getIndex( blk, "Bone List" ); - int ct = nif->rowCount( bones ); + auto iBones = nif->getIndex( blk, "Bone List" ); + int ct = nif->rowCount( iBones ); for ( int i = 0; i < ct; i++ ) { - auto b = bones.child( i, 0 ); + auto b = iBones.child( i, 0 ); boneSphere( nif, b ); } } diff --git a/src/gl/bsshape.h b/src/gl/bsshape.h index e0ade1f25..4064ddb3b 100644 --- a/src/gl/bsshape.h +++ b/src/gl/bsshape.h @@ -47,8 +47,8 @@ class BSShape : public Shape QString skinDataName; QString skinInstName; - int numVerts; - int numTris; + int numVerts = 0; + int numTris = 0; Vector3 bsphereCenter; float bsphereRadius = 0.0; diff --git a/src/gl/controllers.cpp b/src/gl/controllers.cpp index 4443ac618..a0a9ce681 100644 --- a/src/gl/controllers.cpp +++ b/src/gl/controllers.cpp @@ -111,7 +111,7 @@ void ControllerManager::setSequence( const QString & seqname ) for ( int r = 0; r < nif->rowCount( iCtrlBlcks ); r++ ) { QModelIndex iCB = iCtrlBlcks.child( r, 0 ); - QModelIndex iInterpolator = nif->getBlock( nif->getLink( iCB, "Interpolator" ), "NiInterpolator" ); + QModelIndex iInterp = nif->getBlock( nif->getLink( iCB, "Interpolator" ), "NiInterpolator" ); QModelIndex iController = nif->getBlock( nif->getLink( iCB, "Controller" ), "NiTimeController" ); @@ -156,7 +156,7 @@ void ControllerManager::setSequence( const QString & seqname ) continue; if ( ctrltype == "NiTransformController" && multiTargetTransformer ) { - if ( multiTargetTransformer->setInterpolator( node, iInterpolator ) ) { + if ( multiTargetTransformer->setInterpolator( node, iInterp ) ) { multiTargetTransformer->start = start; multiTargetTransformer->stop = stop; multiTargetTransformer->phase = phase; @@ -175,7 +175,7 @@ void ControllerManager::setSequence( const QString & seqname ) auto ctrl = node->findController( proptype, iController ); if ( ctrl ) { - ctrl->setInterpolator( iInterpolator ); + ctrl->setInterpolator( iInterp ); } continue; } @@ -188,7 +188,7 @@ void ControllerManager::setSequence( const QString & seqname ) ctrl->phase = phase; ctrl->frequency = frequency; - ctrl->setInterpolator( iInterpolator ); + ctrl->setInterpolator( iInterp ); } } } @@ -252,21 +252,21 @@ void TransformController::updateTime( float time ) } } -void TransformController::setInterpolator( const QModelIndex & iBlock ) +void TransformController::setInterpolator( const QModelIndex & idx ) { - const NifModel * nif = static_cast(iBlock.model()); + const NifModel * nif = static_cast(idx.model()); - if ( nif && iBlock.isValid() ) { + if ( nif && idx.isValid() ) { if ( interpolator ) { delete interpolator; interpolator = 0; } - if ( nif->isNiBlock( iBlock, "NiBSplineCompTransformInterpolator" ) ) { - iInterpolator = iBlock; + if ( nif->isNiBlock( idx, "NiBSplineCompTransformInterpolator" ) ) { + iInterpolator = idx; interpolator = new BSplineTransformInterpolator( this ); - } else if ( nif->isNiBlock( iBlock, "NiTransformInterpolator" ) ) { - iInterpolator = iBlock; + } else if ( nif->isNiBlock( idx, "NiTransformInterpolator" ) ) { + iInterpolator = idx; interpolator = new TransformInterpolator( this ); } @@ -325,11 +325,11 @@ bool MultiTargetTransformController::update( const NifModel * nif, const QModelI return false; } -bool MultiTargetTransformController::setInterpolator( Node * node, const QModelIndex & iInterpolator ) +bool MultiTargetTransformController::setInterpolator( Node * node, const QModelIndex & idx ) { - const NifModel * nif = static_cast(iInterpolator.model()); + const NifModel * nif = static_cast(idx.model()); - if ( !nif || !iInterpolator.isValid() ) + if ( !nif || !idx.isValid() ) return false; QMutableListIterator it( extraTargets ); @@ -343,14 +343,14 @@ bool MultiTargetTransformController::setInterpolator( Node * node, const QModelI it.value().second = 0; } - if ( nif->isNiBlock( iInterpolator, "NiBSplineCompTransformInterpolator" ) ) { + if ( nif->isNiBlock( idx, "NiBSplineCompTransformInterpolator" ) ) { it.value().second = new BSplineTransformInterpolator( this ); - } else if ( nif->isNiBlock( iInterpolator, "NiTransformInterpolator" ) ) { + } else if ( nif->isNiBlock( idx, "NiTransformInterpolator" ) ) { it.value().second = new TransformInterpolator( this ); } if ( it.value().second ) { - it.value().second->update( nif, iInterpolator ); + it.value().second->update( nif, idx ); } return true; @@ -590,12 +590,12 @@ bool ParticleController::update( const NifModel * nif, const QModelIndex & index if ( iParticles.isValid() ) { emitMax = nif->get( iBlock, "Num Particles" ); - int active = nif->get( iBlock, "Num Valid" ); + int numValid = nif->get( iBlock, "Num Valid" ); //iParticles = nif->getIndex( iParticles, "Particles" ); //if ( iParticles.isValid() ) //{ - for ( int p = 0; p < active && p < nif->rowCount( iParticles ); p++ ) { + for ( int p = 0; p < numValid && p < nif->rowCount( iParticles ); p++ ) { Particle particle; particle.velocity = nif->get( iParticles.child( p, 0 ), "Velocity" ); particle.lifetime = nif->get( iParticles.child( p, 0 ), "Lifetime" ); @@ -754,15 +754,15 @@ void ParticleController::moveParticle( Particle & p, float deltaTime ) p.position += p.velocity * deltaTime; } -void ParticleController::sizeParticle( Particle & p, float & size ) +void ParticleController::sizeParticle( Particle & p, float & sz ) { - size = 1.0; + sz = 1.0; if ( grow > 0 && p.lifetime < grow ) - size *= p.lifetime / grow; + sz *= p.lifetime / grow; if ( fade > 0 && p.lifespan - p.lifetime < fade ) - size *= (p.lifespan - p.lifetime) / fade; + sz *= (p.lifespan - p.lifetime) / fade; } void ParticleController::colorParticle( Particle & p, Color4 & color ) @@ -777,14 +777,14 @@ void ParticleController::colorParticle( Particle & p, Color4 & color ) // `BSNiAlphaPropertyTestRefController` AlphaController::AlphaController( AlphaProperty * prop, const QModelIndex & index ) - : Controller( index ), alphaProp( prop ), lAlpha( 0 ) + : Controller( index ), alphaProp( prop ) { } // `NiAlphaController` AlphaController::AlphaController( MaterialProperty * prop, const QModelIndex & index ) - : Controller( index ), materialProp( prop ), lAlpha( 0 ) + : Controller( index ), materialProp( prop ) { } @@ -813,7 +813,7 @@ void AlphaController::updateTime( float time ) // `NiMaterialColorController` MaterialColorController::MaterialColorController( MaterialProperty * prop, const QModelIndex & index ) - : Controller( index ), target( prop ), lColor( 0 ), tColor( tAmbient ) + : Controller( index ), target( prop ) { } @@ -862,12 +862,12 @@ bool MaterialColorController::update( const NifModel * nif, const QModelIndex & // `NiFlipController` TexFlipController::TexFlipController( TexturingProperty * prop, const QModelIndex & index ) - : Controller( index ), target( prop ), flipDelta( 0 ), flipSlot( 0 ) + : Controller( index ), target( prop ) { } TexFlipController::TexFlipController( TextureProperty * prop, const QModelIndex & index ) - : Controller( index ), oldTarget( prop ), flipDelta( 0 ), flipSlot( 0 ) + : Controller( index ), oldTarget( prop ) { } @@ -915,7 +915,7 @@ bool TexFlipController::update( const NifModel * nif, const QModelIndex & index // `NiTextureTransformController` TexTransController::TexTransController( TexturingProperty * prop, const QModelIndex & index ) - : Controller( index ), target( prop ), texSlot( 0 ), texOP( 0 ) + : Controller( index ), target( prop ) { } diff --git a/src/gl/controllers.h b/src/gl/controllers.h index 6ac824d6b..d74367aec 100644 --- a/src/gl/controllers.h +++ b/src/gl/controllers.h @@ -85,7 +85,7 @@ class TransformController final : public Controller void updateTime( float time ) override final; - void setInterpolator( const QModelIndex & iBlock ) override final; + void setInterpolator( const QModelIndex & idx ) override final; protected: QPointer target; @@ -105,7 +105,7 @@ class MultiTargetTransformController final : public Controller bool update( const NifModel * nif, const QModelIndex & index ) override final; - bool setInterpolator( Node * node, const QModelIndex & iInterpolator ); + bool setInterpolator( Node * node, const QModelIndex & idx ); protected: QPointer target; @@ -170,7 +170,7 @@ class UVController final : public Controller protected: QPointer target; - int luv; + int luv = 0; }; @@ -184,13 +184,13 @@ class ParticleController final : public Controller Vector3 position; Vector3 velocity; Vector3 unknown; - float lifetime; - float lifespan; - float lasttime; - short y; - short vertex; + float lifetime = 0; + float lifespan = 0; + float lasttime = 0; + short y = 0; + short vertex = 0; - Particle() : lifetime( 0 ), lifespan( 0 ) + Particle() { } }; @@ -206,21 +206,21 @@ class ParticleController final : public Controller QPointer target; - float emitStart, emitStop, emitRate, emitLast, emitAccu, emitMax; + float emitStart = 0, emitStop = 0, emitRate = 0, emitLast = 0, emitAccu = 0, emitMax = 0; QPointer emitNode; Vector3 emitRadius; - float spd, spdRnd; - float ttl, ttlRnd; + float spd = 0, spdRnd = 0; + float ttl = 0, ttlRnd = 0; - float inc, incRnd; - float dec, decRnd; + float inc = 0, incRnd = 0; + float dec = 0, decRnd = 0; - float size; - float grow; - float fade; + float size = 0; + float grow = 0; + float fade = 0; - float localtime; + float localtime = 0; QList iExtras; QPersistentModelIndex iColorKeys; @@ -263,7 +263,7 @@ class AlphaController final : public Controller QPointer materialProp; QPointer alphaProp; - int lAlpha; + int lAlpha = 0; }; @@ -280,8 +280,8 @@ class MaterialColorController final : public Controller protected: QPointer target; //!< The MaterialProperty being controlled - int lColor; //!< Last interpolation time - int tColor; //!< The color slot being controlled + int lColor = 0; //!< Last interpolation time + int tColor = tAmbient; //!< The color slot being controlled //! Color slots that can be controlled enum @@ -310,10 +310,10 @@ class TexFlipController final : public Controller QPointer target; QPointer oldTarget; - float flipDelta; - int flipSlot; + float flipDelta = 0; + int flipSlot = 0; - int flipLast; + int flipLast = 0; QPersistentModelIndex iSources; }; @@ -332,10 +332,10 @@ class TexTransController final : public Controller protected: QPointer target; - int texSlot; - int texOP; + int texSlot = 0; + int texOP = 0; - int lX; + int lX = 0; }; namespace EffectFloat @@ -369,7 +369,7 @@ class EffectFloatController final : public Controller protected: QPointer target; - EffectFloat::Variable variable; + EffectFloat::Variable variable = EffectFloat::Emissive_Multiple; }; @@ -386,7 +386,7 @@ class EffectColorController final : public Controller protected: QPointer target; - int variable; + int variable = 0; }; namespace LightingFloat @@ -420,7 +420,7 @@ class LightingFloatController final : public Controller protected: QPointer target; - LightingFloat::Variable variable; + LightingFloat::Variable variable = LightingFloat::Refraction_Strength; }; @@ -437,7 +437,7 @@ class LightingColorController final : public Controller protected: QPointer target; - int variable; + int variable = 0; }; diff --git a/src/gl/glcontroller.cpp b/src/gl/glcontroller.cpp index 922103bb9..74aa9a33d 100644 --- a/src/gl/glcontroller.cpp +++ b/src/gl/glcontroller.cpp @@ -735,8 +735,7 @@ bool TransformInterpolator::updateTransform( Transform & tm, float time ) } -BSplineTransformInterpolator::BSplineTransformInterpolator( Controller * owner ) : TransformInterpolator( owner ), - lTransOff( USHRT_MAX ), lRotateOff( USHRT_MAX ), lScaleOff( USHRT_MAX ), nCtrl( 0 ), degree( 3 ) +BSplineTransformInterpolator::BSplineTransformInterpolator( Controller * owner ) : TransformInterpolator( owner ) { } diff --git a/src/gl/glcontroller.h b/src/gl/glcontroller.h index 41fbb6ff3..8415f329b 100644 --- a/src/gl/glcontroller.h +++ b/src/gl/glcontroller.h @@ -51,18 +51,18 @@ class Controller Controller( const QModelIndex & index ); virtual ~Controller() {} - float start; - float stop; - float phase; - float frequency; + float start = 0; + float stop = 0; + float phase = 0; + float frequency = 0; //! Extrapolation type enum Extrapolation { Cyclic = 0, Reverse = 1, Constant = 2 - } extrapolation; + } extrapolation = Cyclic; - bool active; + bool active = false; //! Find the model index of the controller QModelIndex index() const { return iBlock; } @@ -168,14 +168,14 @@ class BSplineTransformInterpolator : public TransformInterpolator bool updateTransform( Transform & tm, float time ) override; protected: - float start, stop; + float start = 0, stop = 0; QPersistentModelIndex iControl, iSpline, iBasis; QPersistentModelIndex lTrans, lRotate, lScale; - uint lTransOff, lRotateOff, lScaleOff; - float lTransMult, lRotateMult, lScaleMult; - float lTransBias, lRotateBias, lScaleBias; - uint nCtrl; - int degree; + uint lTransOff = USHRT_MAX, lRotateOff = USHRT_MAX, lScaleOff = USHRT_MAX; + float lTransMult = 0, lRotateMult = 0, lScaleMult = 0; + float lTransBias = 0, lRotateBias = 0, lScaleBias = 0; + uint nCtrl = 0; + int degree = 3; }; diff --git a/src/gl/glmesh.h b/src/gl/glmesh.h index 02893129f..805041496 100644 --- a/src/gl/glmesh.h +++ b/src/gl/glmesh.h @@ -121,7 +121,7 @@ class Shape : public Node //! Toggle for skinning bool doSkinning = false; - int skeletonRoot; + int skeletonRoot = 0; Transform skeletonTrans; QVector bones; QVector weights; @@ -149,7 +149,7 @@ class Shape : public Node bool translucent = false; mutable BoundSphere boundSphere; - mutable bool updateBounds; + mutable bool updateBounds = false; }; //! A mesh diff --git a/src/gl/glnode.cpp b/src/gl/glnode.cpp index 01c660055..d0e6aabc1 100644 --- a/src/gl/glnode.cpp +++ b/src/gl/glnode.cpp @@ -455,13 +455,13 @@ Node * Node::findChild( int id ) const return nullptr; } -Node * Node::findChild( const QString & name ) const +Node * Node::findChild( const QString & str ) const { - if ( this->name == name ) + if ( this->name == str ) return const_cast( this ); for ( Node * child : children.list() ) { - Node * n = child->findChild( name ); + Node * n = child->findChild( str ); if ( n ) return n; @@ -770,7 +770,7 @@ void DrawTriangleIndex( QVector const & verts, Triangle const & tri, in void drawHvkShape( const NifModel * nif, const QModelIndex & iShape, QStack & stack, const Scene * scene, const float origin_color3fv[3] ) { - QString name = nif->itemName( iShape ); + QString name = (nif) ? nif->itemName( iShape ) : ""; bool extraData = (name == "hkPackedNiTriStripsData"); diff --git a/src/gl/glnode.h b/src/gl/glnode.h index 0337ee5ae..38b988171 100644 --- a/src/gl/glnode.h +++ b/src/gl/glnode.h @@ -130,7 +130,7 @@ class Node : public IControllable bool isPresorted() const { return presorted; } Node * findChild( int id ) const; - Node * findChild( const QString & name ) const; + Node * findChild( const QString & str ) const; Node * findParent( int id ) const; Node * parentNode() const { return parent; } diff --git a/src/gl/glparticles.h b/src/gl/glparticles.h index b98698cb9..37a529979 100644 --- a/src/gl/glparticles.h +++ b/src/gl/glparticles.h @@ -62,15 +62,15 @@ class Particles : public Node void setController( const NifModel * nif, const QModelIndex & controller ) override; QPersistentModelIndex iData; - bool upData; + bool upData = false; QVector verts; QVector colors; QVector sizes; QVector transVerts; - int active; - float size; + int active = 0; + float size = 0; }; diff --git a/src/gl/glproperty.cpp b/src/gl/glproperty.cpp index 1ad9702cb..cd65e86f2 100644 --- a/src/gl/glproperty.cpp +++ b/src/gl/glproperty.cpp @@ -1112,11 +1112,11 @@ void BSLightingShaderProperty::updateParams( const NifModel * nif, const QModelI if ( !m ) { alpha = nif->get( prop, "Alpha" ); - auto uvScale = nif->get( prop, "UV Scale" ); - auto uvOffset = nif->get( prop, "UV Offset" ); + auto scale = nif->get( prop, "UV Scale" ); + auto offset = nif->get( prop, "UV Offset" ); - setUvScale( uvScale[0], uvScale[1] ); - setUvOffset( uvOffset[0], uvOffset[1] ); + setUvScale( scale[0], scale[1] ); + setUvOffset( offset[0], offset[1] ); setClampMode( nif->get( prop, "Texture Clamp Mode" ) ); // Specular @@ -1453,11 +1453,11 @@ void BSEffectShaderProperty::updateParams( const NifModel * nif, const QModelInd environmentReflection = nif->get( prop, "Environment Map Scale" ); } - auto uvScale = nif->get( prop, "UV Scale" ); - auto uvOffset = nif->get( prop, "UV Offset" ); + auto scale = nif->get( prop, "UV Scale" ); + auto offset = nif->get( prop, "UV Offset" ); - setUvScale( uvScale[0], uvScale[1] ); - setUvOffset( uvOffset[0], uvOffset[1] ); + setUvScale( scale[0], scale[1] ); + setUvOffset( offset[0], offset[1] ); setClampMode( nif->get( prop, "Texture Clamp Mode" ) ); if ( hasSF2( ShaderFlags::SLSF2_Effect_Lighting ) ) diff --git a/src/gl/glproperty.h b/src/gl/glproperty.h index ad2bafa1a..241f9376d 100644 --- a/src/gl/glproperty.h +++ b/src/gl/glproperty.h @@ -57,9 +57,9 @@ class Property : public IControllable protected: //! Protected constructor; see IControllable() - Property( Scene * scene, const QModelIndex & index ) : IControllable( scene, index ), ref( 0 ) {} + Property( Scene * scene, const QModelIndex & index ) : IControllable( scene, index ) {} - int ref; + int ref = 0; public: /*! Creates a Property based on the specified index of the specified model @@ -160,9 +160,9 @@ class AlphaProperty final : public Property protected: void setController( const NifModel * nif, const QModelIndex & controller ) override final; - bool alphaBlend, alphaTest, alphaSort; - GLenum alphaSrc, alphaDst, alphaFunc; - GLfloat alphaThreshold; + bool alphaBlend = false, alphaTest = false, alphaSort = false; + GLenum alphaSrc = 0, alphaDst = 0, alphaFunc = 0; + GLfloat alphaThreshold = 0; }; REGISTER_PROPERTY( AlphaProperty, Alpha ) @@ -184,9 +184,9 @@ class ZBufferProperty final : public Property friend void glProperty( ZBufferProperty * ); protected: - bool depthTest; - bool depthMask; - GLenum depthFunc; + bool depthTest = false; + bool depthMask = false; + GLenum depthFunc = 0; }; REGISTER_PROPERTY( ZBufferProperty, ZBuffer ) @@ -204,15 +204,15 @@ class TexturingProperty final : public Property struct TexDesc { QPersistentModelIndex iSource; - GLenum filter; - GLint wrapS, wrapT; - int coordset; + GLenum filter = 0; + GLint wrapS = 0, wrapT = 0; + int coordset = 0; - bool hasTransform; + bool hasTransform = false; Vector2 translation; Vector2 tiling; - float rotation; + float rotation = 0; Vector2 center; }; @@ -292,8 +292,8 @@ class MaterialProperty final : public Property protected: Color4 ambient, diffuse, specular, emissive; - GLfloat shininess, alpha; - bool overridden; + GLfloat shininess = 0, alpha = 0; + bool overridden = false; void setController( const NifModel * nif, const QModelIndex & controller ) override final; }; @@ -314,7 +314,7 @@ class SpecularProperty final : public Property friend void glProperty( class MaterialProperty *, class SpecularProperty * ); protected: - bool spec; + bool spec = false; }; REGISTER_PROPERTY( SpecularProperty, Specular ) @@ -333,7 +333,7 @@ class WireframeProperty final : public Property friend void glProperty( WireframeProperty * ); protected: - bool wire; + bool wire = false; }; REGISTER_PROPERTY( WireframeProperty, Wireframe ) @@ -352,8 +352,8 @@ class VertexColorProperty final : public Property friend void glProperty( VertexColorProperty *, bool vertexcolors ); protected: - int lightmode; - int vertexmode; + int lightmode = 0; + int vertexmode = 0; }; REGISTER_PROPERTY( VertexColorProperty, VertexColor ) @@ -372,18 +372,17 @@ class StencilProperty final : public Property friend void glProperty( StencilProperty * ); protected: - bool stencil; + bool stencil = false; - GLenum func; - GLint ref; - GLuint mask; + GLenum func = 0; + GLuint mask = 0; - GLenum failop; - GLenum zfailop; - GLenum zpassop; + GLenum failop = 0; + GLenum zfailop = 0; + GLenum zpassop = 0; - bool cullEnable; - GLenum cullMode; + bool cullEnable = false; + GLenum cullMode = 0; }; REGISTER_PROPERTY( StencilProperty, Stencil ) @@ -577,7 +576,7 @@ class BSShaderLightingProperty : public Property UVScale uvScale; UVOffset uvOffset; - TexClampMode clampMode; + TexClampMode clampMode = CLAMP_S_CLAMP_T; bool depthTest = false; bool depthWrite = false; @@ -679,7 +678,7 @@ class BSLightingShaderProperty final : public BSShaderLightingProperty protected: void setController( const NifModel * nif, const QModelIndex & controller ) override final; - ShaderFlags::ShaderType shaderType; + ShaderFlags::ShaderType shaderType = ShaderFlags::ST_Default; Color3 emissiveColor; Color3 specularColor; @@ -698,10 +697,10 @@ class BSLightingShaderProperty final : public BSShaderLightingProperty float environmentReflection = 0.0; // Multi-layer properties - float innerThickness; + float innerThickness = 1.0; UVScale innerTextureScale; - float outerRefractionStrength; - float outerReflectionStrength; + float outerRefractionStrength = 0.0; + float outerReflectionStrength = 1.0; }; REGISTER_PROPERTY( BSLightingShaderProperty, ShaderLighting ) @@ -767,7 +766,7 @@ class BSEffectShaderProperty final : public BSShaderLightingProperty void setController( const NifModel * nif, const QModelIndex & controller ) override final; Color4 emissiveColor; - float emissiveMult; + float emissiveMult = 1.0; float lightingInfluence = 0.0; float environmentReflection = 0.0; @@ -806,7 +805,7 @@ class BSWaterShaderProperty final : public BSShaderLightingProperty void setWaterShaderFlags( unsigned int ); protected: - WaterShaderFlags::SF1 waterShaderFlags; + WaterShaderFlags::SF1 waterShaderFlags = WaterShaderFlags::SF1(0); }; REGISTER_PROPERTY( BSWaterShaderProperty, ShaderLighting ) diff --git a/src/gl/glscene.h b/src/gl/glscene.h index 1fd70e76a..781ca5472 100644 --- a/src/gl/glscene.h +++ b/src/gl/glscene.h @@ -193,7 +193,7 @@ public slots: protected: mutable bool sceneBoundsValid, timeBoundsValid; mutable BoundSphere bndSphere; - mutable float tMin, tMax; + mutable float tMin = 0, tMax = 0; void updateTimeBounds() const; }; diff --git a/src/gl/gltex.cpp b/src/gl/gltex.cpp index e08176594..afc33027d 100644 --- a/src/gl/gltex.cpp +++ b/src/gl/gltex.cpp @@ -399,7 +399,7 @@ int TexCache::bind( const QModelIndex & iSource ) embedTextures.insert( iData, tx ); texLoad( iData, tx->format, tx->width, tx->height, tx->mipmaps ); } - catch ( QString e ) { + catch ( QString & e ) { tx->status = e; } } @@ -564,7 +564,7 @@ void TexCache::Tex::load() { texLoad( filepath, format, width, height, mipmaps, data ); } - catch ( QString e ) + catch ( QString & e ) { status = e; } @@ -585,7 +585,7 @@ void TexCache::Tex::loadCube() { texLoadCube( filepath, format, width, height, mipmaps, data, id ); } - catch ( QString e ) + catch ( QString & e ) { status = e; } diff --git a/src/gl/gltexloaders.cpp b/src/gl/gltexloaders.cpp index 96dea9f5b..4fb84cd35 100644 --- a/src/gl/gltexloaders.cpp +++ b/src/gl/gltexloaders.cpp @@ -995,13 +995,13 @@ GLuint texLoadBMP( QIODevice & f, QString & texformat ) // Since when can a BMP contain DXT compressed textures? case FOURCC_DXT5: texformat += " (DXT5)"; - return texLoadDXT( f, compression, width, height, 1, true ); + return texLoadDXT( f, compression, 16, width, height, 1, true ); case FOURCC_DXT3: texformat += " (DXT3)"; - return texLoadDXT( f, compression, width, height, 1, true ); + return texLoadDXT( f, compression, 16, width, height, 1, true ); case FOURCC_DXT1: texformat += " (DXT1)"; - return texLoadDXT( f, compression, width, height, 1, true ); + return texLoadDXT( f, compression, 8, width, height, 1, true ); } throw QString( "unknown image sub format" ); @@ -1249,7 +1249,8 @@ bool texLoad( const QString & filepath, QString & format, GLuint & width, GLuint } -bool texLoadCube( const QString & filepath, QString & format, GLuint & width, GLuint & height, GLuint & mipmaps, QByteArray & data, GLuint id ) +bool texLoadCube( const QString & filepath, QString & format, GLuint & width, GLuint & height, + GLuint & mipmaps, QByteArray & data, GLuint id ) { Q_UNUSED( format ); @@ -1322,7 +1323,7 @@ bool texCanLoad( const QString & filepath ) } -bool texSaveDDS( const QModelIndex & index, const QString & filepath, GLuint & width, GLuint & height, GLuint & mipmaps ) +bool texSaveDDS( const QModelIndex & index, const QString & filepath, const GLuint & width, const GLuint & height, const GLuint & mipmaps ) { const NifModel * nif = qobject_cast( index.model() ); quint32 format = nif->get( index, "Pixel Format" ); @@ -1589,7 +1590,7 @@ bool texSaveDDS( const QModelIndex & index, const QString & filepath, GLuint & w } -bool texSaveTGA( const QModelIndex & index, const QString & filepath, GLuint & width, GLuint & height ) +bool texSaveTGA( const QModelIndex & index, const QString & filepath, const GLuint & width, const GLuint & height ) { Q_UNUSED( index ); //const NifModel * nif = qobject_cast( index.model() ); diff --git a/src/gl/gltexloaders.h b/src/gl/gltexloaders.h index afbe1f795..f6d65f64c 100644 --- a/src/gl/gltexloaders.h +++ b/src/gl/gltexloaders.h @@ -96,7 +96,7 @@ extern bool texCanLoad( const QString & filepath ); * @param mipmaps The number of mipmaps present * @return True if the save was successful, false otherwise */ -bool texSaveDDS( const QModelIndex & index, const QString & filepath, GLuint & width, GLuint & height, GLuint & mipmaps ); +bool texSaveDDS( const QModelIndex & index, const QString & filepath, const GLuint & width, const GLuint & height, const GLuint & mipmaps ); /*! Save pixel data to a TGA file * @@ -106,7 +106,7 @@ bool texSaveDDS( const QModelIndex & index, const QString & filepath, GLuint & w * @param height The height of the texture * @return True if the save was successful, false otherwise */ -bool texSaveTGA( const QModelIndex & index, const QString & filepath, GLuint & width, GLuint & height ); +bool texSaveTGA( const QModelIndex & index, const QString & filepath, const GLuint & width, const GLuint & height ); /*! Save a file to pixel data * diff --git a/src/gl/gltools.cpp b/src/gl/gltools.cpp index 3e2abd1ee..84a41a196 100644 --- a/src/gl/gltools.cpp +++ b/src/gl/gltools.cpp @@ -486,8 +486,6 @@ void drawRagdollCone( Vector3 pivot, Vector3 twist, Vector3 plane, float coneAng Vector3 x = Vector3::crossproduct( z, y ); x = x * sin( coneAngle ); - y = y; - z = z; glBegin( GL_TRIANGLE_FAN ); glVertex( pivot ); diff --git a/src/gl/gltools.h b/src/gl/gltools.h index fc54940d9..cfaa05980 100644 --- a/src/gl/gltools.h +++ b/src/gl/gltools.h @@ -81,15 +81,16 @@ class VertexWeight final class BoneWeights final { public: - BoneWeights() { bone = 0; } + BoneWeights() {} BoneWeights( const NifModel * nif, const QModelIndex & index, int b, int vcnt = 0 ); void setTransform( const NifModel * nif, const QModelIndex & index ); Transform trans; - Vector3 center; float radius; + Vector3 center; + float radius = 0; Vector3 tcenter; - int bone; + int bone = 0; QVector weights; }; diff --git a/src/gl/renderer.cpp b/src/gl/renderer.cpp index 2693136ae..843a54654 100644 --- a/src/gl/renderer.cpp +++ b/src/gl/renderer.cpp @@ -231,7 +231,7 @@ bool Renderer::Shader::load( const QString & filepath ) throw errlog; } } - catch ( QString err ) + catch ( QString & err ) { status = false; Message::append( QObject::tr( "There were errors during shader compilation" ), QString( "%1:\r\n\r\n%2" ).arg( name ).arg( err ) ); @@ -317,18 +317,18 @@ bool Renderer::Program::load( const QString & filepath, Renderer * renderer ) QStringList list = line.split( " " ); bool ok; int unit = list.value( 0 ).toInt( &ok ); - QString id = list.value( 1 ).toLower(); + QString idStr = list.value( 1 ).toLower(); - if ( !ok || id.isEmpty() ) + if ( !ok || idStr.isEmpty() ) throw QString( "malformed texcoord tag" ); - if ( id != "tangents" && id != "bitangents" && TexturingProperty::getId( id ) < 0 ) - throw QString( "texcoord tag referres to unknown texture id '%1'" ).arg( id ); + if ( idStr != "tangents" && idStr != "bitangents" && TexturingProperty::getId( idStr ) < 0 ) + throw QString( "texcoord tag referres to unknown texture id '%1'" ).arg( idStr ); if ( texcoords.contains( unit ) ) throw QString( "texture unit %1 is assigned twiced" ).arg( unit ); - texcoords.insert( unit, id ); + texcoords.insert( unit, idStr ); } } @@ -352,7 +352,7 @@ bool Renderer::Program::load( const QString & filepath, Renderer * renderer ) } } } - catch ( QString x ) + catch ( QString & x ) { status = false; Message::append( QObject::tr( "There were errors during shader compilation" ), QString( "%1:\r\n\r\n%2" ).arg( name ).arg( x ) ); @@ -855,7 +855,6 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & uni4m( "worldMatrix", mesh->worldTrans().toMatrix4() ); clamp = mesh->bsesp->getClampMode(); - clamp = TexClampMode(clamp); if ( !uniSampler( "SourceTexture", 0, white, clamp ) ) return false; diff --git a/src/glview.cpp b/src/glview.cpp index a7b66a62d..7d4a84d3a 100644 --- a/src/glview.cpp +++ b/src/glview.cpp @@ -238,7 +238,7 @@ void GLView::updateSettings() cfg.fov = settings.value( "General/Camera/Field Of View" ).toFloat(); cfg.moveSpd = settings.value( "General/Camera/Movement Speed" ).toFloat(); cfg.rotSpd = settings.value( "General/Camera/Rotation Speed" ).toFloat(); - cfg.upAxis = settings.value( "General/Up Axis", 2 ).toInt(); + cfg.upAxis = UpAxis(settings.value( "General/Up Axis", ZAxis ).toInt()); settings.endGroup(); } @@ -447,7 +447,6 @@ void GLView::paintGL() ap( 2, 0 ) = 1; ap( 2, 1 ) = 0; ap( 2, 2 ) = 0; } - Transform viewTrans; viewTrans.rotation.fromEuler( Rot[0] / 180.0 * PI, Rot[1] / 180.0 * PI, Rot[2] / 180.0 * PI ); viewTrans.rotation = viewTrans.rotation * ap; viewTrans.translation = viewTrans.rotation * Pos; @@ -1240,30 +1239,30 @@ void GLView::setSceneSequence( const QString & seqname ) // TODO: Multiple user views, ala Recent Files void GLView::saveUserView() { - QSettings cfg; - cfg.beginGroup( "GLView" ); - cfg.beginGroup( "User View" ); - cfg.setValue( "RotX", Rot[0] ); - cfg.setValue( "RotY", Rot[1] ); - cfg.setValue( "RotZ", Rot[2] ); - cfg.setValue( "PosX", Pos[0] ); - cfg.setValue( "PosY", Pos[1] ); - cfg.setValue( "PosZ", Pos[2] ); - cfg.setValue( "Dist", Dist ); - cfg.endGroup(); - cfg.endGroup(); + QSettings settings; + settings.beginGroup( "GLView" ); + settings.beginGroup( "User View" ); + settings.setValue( "RotX", Rot[0] ); + settings.setValue( "RotY", Rot[1] ); + settings.setValue( "RotZ", Rot[2] ); + settings.setValue( "PosX", Pos[0] ); + settings.setValue( "PosY", Pos[1] ); + settings.setValue( "PosZ", Pos[2] ); + settings.setValue( "Dist", Dist ); + settings.endGroup(); + settings.endGroup(); } void GLView::loadUserView() { - QSettings cfg; - cfg.beginGroup( "GLView" ); - cfg.beginGroup( "User View" ); - setRotation( cfg.value( "RotX" ).toDouble(), cfg.value( "RotY" ).toDouble(), cfg.value( "RotZ" ).toDouble() ); - setPosition( cfg.value( "PosX" ).toDouble(), cfg.value( "PosY" ).toDouble(), cfg.value( "PosZ" ).toDouble() ); - setDistance( cfg.value( "Dist" ).toDouble() ); - cfg.endGroup(); - cfg.endGroup(); + QSettings settings; + settings.beginGroup( "GLView" ); + settings.beginGroup( "User View" ); + setRotation( settings.value( "RotX" ).toDouble(), settings.value( "RotY" ).toDouble(), settings.value( "RotZ" ).toDouble() ); + setPosition( settings.value( "PosX" ).toDouble(), settings.value( "PosY" ).toDouble(), settings.value( "PosZ" ).toDouble() ); + setDistance( settings.value( "Dist" ).toDouble() ); + settings.endGroup(); + settings.endGroup(); } void GLView::advanceGears() @@ -1393,9 +1392,9 @@ void GLView::saveImage() lay->addWidget( niffileDir, 1, 1, 1, 1 ); // Save JPEG Quality - QSettings cfg; - int jpegQuality = cfg.value( "JPEG/Quality", 90 ).toInt(); - cfg.setValue( "JPEG/Quality", jpegQuality ); + QSettings settings; + int jpegQuality = settings.value( "JPEG/Quality", 90 ).toInt(); + settings.setValue( "JPEG/Quality", jpegQuality ); QHBoxLayout * pixBox = new QHBoxLayout; pixBox->setAlignment( Qt::AlignRight ); @@ -1482,8 +1481,8 @@ void GLView::saveImage() connect( btnOk, &QPushButton::clicked, [&]() { // Save JPEG Quality - QSettings cfg; - cfg.setValue( "JPEG/Quality", pixQuality->value() ); + QSettings settings; + settings.setValue( "JPEG/Quality", pixQuality->value() ); // TODO: Set up creation of screenshots directory in Options if ( nifskopeDir->isChecked() ) { diff --git a/src/glview.h b/src/glview.h index 425e8de13..cf9c621a3 100644 --- a/src/glview.h +++ b/src/glview.h @@ -270,7 +270,7 @@ protected slots: float moveSpd = 350; float rotSpd = 45; - int upAxis; + UpAxis upAxis = ZAxis; } cfg; private slots: diff --git a/src/importex/3ds.cpp b/src/importex/3ds.cpp index ef97eabd0..61ff58f8e 100644 --- a/src/importex/3ds.cpp +++ b/src/importex/3ds.cpp @@ -76,25 +76,24 @@ struct objMesh struct objKeyframe { Vector3 pos; - float rotAngle; + float rotAngle = 0; Vector3 rotAxis; - float scale; + float scale = 0; objKeyframe() - : pos( 0.0f, 0.0f, 0.0f ), rotAngle( 0 ), rotAxis( 0.0f, 0.0f, 0.0f ), scale( 0.0f ) { } }; struct objKfSequence { - short objectId; + short objectId = 0; QString objectName; - long startTime, endTime, curTime; + long startTime = 0, endTime = 0, curTime = 0; Vector3 pivot; QMap frames; - objKfSequence() : pivot( 0.0f, 0.0f, 0.0f ) + objKfSequence() { } }; diff --git a/src/importex/obj.cpp b/src/importex/obj.cpp index 523faee8a..136b39750 100644 --- a/src/importex/obj.cpp +++ b/src/importex/obj.cpp @@ -806,11 +806,9 @@ void importObj( NifModel * nif, const QModelIndex & index ) iTexSource = nif->insertNiBlock( "NiSourceTexture" ); } - if ( !cBSShaderPPLightingProperty ) // no need of NiTexturingProperty when BSShaderPPLightingProperty is present - nif->setLink( iBaseMap, "Source", nif->getBlockNumber( iTexSource ) ); - if ( !cBSShaderPPLightingProperty ) { // no need of NiTexturingProperty when BSShaderPPLightingProperty is present + nif->setLink( iBaseMap, "Source", nif->getBlockNumber( iTexSource ) ); nif->set( iTexSource, "Pixel Layout", nif->getVersion() == "20.0.0.5" ? 6 : 5 ); nif->set( iTexSource, "Use Mipmaps", 2 ); nif->set( iTexSource, "Alpha Format", 3 ); diff --git a/src/kfmxml.cpp b/src/kfmxml.cpp index 3f7cad869..8570355d0 100644 --- a/src/kfmxml.cpp +++ b/src/kfmxml.cpp @@ -49,18 +49,16 @@ class KfmXmlHandler final : public QXmlDefaultHandler Q_DECLARE_TR_FUNCTIONS( KfmXmlHandler ) public: - KfmXmlHandler() : depth( 0 ), - elements( { "niftoolsxml", "version", "compound", "add" } ), - blk( 0 ) + KfmXmlHandler() { } - int depth; - int stack[10]; - QStringList elements; + int depth = 0; + int stack[10] = {0}; + QStringList elements = { "niftoolsxml", "version", "compound", "add" }; QString errorStr; - NifBlockPtr blk; + NifBlockPtr blk = nullptr; int current() const { diff --git a/src/material.h b/src/material.h index 55d0981a7..799c9b117 100644 --- a/src/material.h +++ b/src/material.h @@ -74,7 +74,7 @@ class Material : public QObject QByteArray data; // Is not JSON format or otherwise unreadable - bool readable; + bool readable = false; // BGSM & BGEM shared variables @@ -82,30 +82,30 @@ class Material : public QObject quint32 tileFlags = 0; bool bTileU = false; bool bTileV = false; - float fUOffset; - float fVOffset; - float fUScale; - float fVScale; - float fAlpha; - quint8 bAlphaBlend; - quint32 iAlphaSrc; - quint32 iAlphaDst; - quint8 iAlphaTestRef; - quint8 bAlphaTest; - quint8 bZBufferWrite; - quint8 bZBufferTest; - quint8 bScreenSpaceReflections; - quint8 bWetnessControl_ScreenSpaceReflections; - quint8 bDecal; - quint8 bTwoSided; - quint8 bDecalNoFade; - quint8 bNonOccluder; - quint8 bRefraction; - quint8 bRefractionFalloff; - float fRefractionPower; - quint8 bEnvironmentMapping; - float fEnvironmentMappingMaskScale; - quint8 bGrayscaleToPaletteColor; + float fUOffset = 0; + float fVOffset = 0; + float fUScale = 1.0; + float fVScale = 1.0; + float fAlpha = 1.0; + quint8 bAlphaBlend = 0; + quint32 iAlphaSrc = 0; + quint32 iAlphaDst = 0; + quint8 iAlphaTestRef = 255; + quint8 bAlphaTest = 0; + quint8 bZBufferWrite = 1; + quint8 bZBufferTest = 1; + quint8 bScreenSpaceReflections = 0; + quint8 bWetnessControl_ScreenSpaceReflections = 0; + quint8 bDecal = 0; + quint8 bTwoSided = 0; + quint8 bDecalNoFade = 0; + quint8 bNonOccluder = 0; + quint8 bRefraction = 0; + quint8 bRefractionFalloff = 0; + float fRefractionPower = 0; + quint8 bEnvironmentMapping = 0; + float fEnvironmentMappingMaskScale = 1.0; + quint8 bGrayscaleToPaletteColor = 1.0; }; @@ -124,55 +124,55 @@ class ShaderMaterial : public Material protected: bool readFile() override final; - quint8 bEnableEditorAlphaRef; - quint8 bRimLighting; - float fRimPower; - float fBacklightPower; - quint8 bSubsurfaceLighting; - float fSubsurfaceLightingRolloff; - quint8 bSpecularEnabled; - float specR, specG, specB; + quint8 bEnableEditorAlphaRef = 0; + quint8 bRimLighting = 0; + float fRimPower = 0; + float fBacklightPower = 0; + quint8 bSubsurfaceLighting = 0; + float fSubsurfaceLightingRolloff = 0.0; + quint8 bSpecularEnabled = 1; + float specR = 1.0, specG = 1.0, specB = 1.0; Color3 cSpecularColor; - float fSpecularMult; - float fSmoothness; - float fFresnelPower; - float fWetnessControl_SpecScale; - float fWetnessControl_SpecPowerScale; - float fWetnessControl_SpecMinvar; - float fWetnessControl_EnvMapScale; - float fWetnessControl_FresnelPower; - float fWetnessControl_Metalness; + float fSpecularMult = 0; + float fSmoothness = 0; + float fFresnelPower = 0; + float fWetnessControl_SpecScale = 0; + float fWetnessControl_SpecPowerScale = 0; + float fWetnessControl_SpecMinvar = 0; + float fWetnessControl_EnvMapScale = 0; + float fWetnessControl_FresnelPower = 0; + float fWetnessControl_Metalness = 0; QString sRootMaterialPath; - quint8 bAnisoLighting; - quint8 bEmitEnabled; + quint8 bAnisoLighting = 0; + quint8 bEmitEnabled = 0; float emitR = 0, emitG = 0, emitB = 0; Color3 cEmittanceColor; - float fEmittanceMult; - quint8 bModelSpaceNormals; - quint8 bExternalEmittance; - quint8 bBackLighting; - quint8 bReceiveShadows; - quint8 bHideSecret; - quint8 bCastShadows; - quint8 bDissolveFade; - quint8 bAssumeShadowmask; - quint8 bGlowmap; - quint8 bEnvironmentMappingWindow; - quint8 bEnvironmentMappingEye; - quint8 bHair; - float hairR, hairG, hairB; + float fEmittanceMult = 0; + quint8 bModelSpaceNormals = 0; + quint8 bExternalEmittance = 0; + quint8 bBackLighting = 0; + quint8 bReceiveShadows = 1; + quint8 bHideSecret = 0; + quint8 bCastShadows = 1; + quint8 bDissolveFade = 0; + quint8 bAssumeShadowmask = 0; + quint8 bGlowmap = 0; + quint8 bEnvironmentMappingWindow = 0; + quint8 bEnvironmentMappingEye = 0; + quint8 bHair = 0; + float hairR = 0, hairG = 0, hairB = 0; Color3 cHairTintColor; - quint8 bTree; - quint8 bFacegen; - quint8 bSkinTint; - quint8 bTessellate; - float fDisplacementTextureBias; - float fDisplacementTextureScale; - float fTessellationPnScale; - float fTessellationBaseFactor; - float fTessellationFadeDistance; - float fGrayscaleToPaletteScale; - quint8 bSkewSpecularAlpha; + quint8 bTree = 0; + quint8 bFacegen = 0; + quint8 bSkinTint = 0; + quint8 bTessellate = 0; + float fDisplacementTextureBias = 0; + float fDisplacementTextureScale = 0; + float fTessellationPnScale = 0; + float fTessellationBaseFactor = 0; + float fTessellationFadeDistance = 0; + float fGrayscaleToPaletteScale = 0; + quint8 bSkewSpecularAlpha = 0; }; @@ -192,22 +192,22 @@ class EffectMaterial : public Material protected: bool readFile() override final; - quint8 bBloodEnabled; - quint8 bEffectLightingEnabled; - quint8 bFalloffEnabled; - quint8 bFalloffColorEnabled; - quint8 bGrayscaleToPaletteAlpha; - quint8 bSoftEnabled; - float baseR = 0, baseG = 0, baseB = 0; + quint8 bBloodEnabled = 0; + quint8 bEffectLightingEnabled = 0; + quint8 bFalloffEnabled = 0; + quint8 bFalloffColorEnabled = 0; + quint8 bGrayscaleToPaletteAlpha = 0; + quint8 bSoftEnabled = 0; + float baseR = 1.0, baseG = 1.0, baseB = 1.0; Color3 cBaseColor; - float fBaseColorScale; - float fFalloffStartAngle; - float fFalloffStopAngle; - float fFalloffStartOpacity; - float fFalloffStopOpacity; - float fLightingInfluence; - quint8 iEnvmapMinLOD; - float fSoftDepth; + float fBaseColorScale = 1.0; + float fFalloffStartAngle = 1.0; + float fFalloffStopAngle = 0; + float fFalloffStartOpacity = 1.0; + float fFalloffStopOpacity = 0; + float fLightingInfluence = 1.0; + quint8 iEnvmapMinLOD = 0; + float fSoftDepth = 100.0; }; diff --git a/src/nifdelegate.cpp b/src/nifdelegate.cpp index c2e8842d2..d93b39b04 100644 --- a/src/nifdelegate.cpp +++ b/src/nifdelegate.cpp @@ -138,8 +138,7 @@ class NifDelegate final : public QItemDelegate virtual void paint( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const override final { - int namerole = NifSkopeDisplayRole; - namerole = (index.isValid() && index.column() == 0) ? Qt::DisplayRole : NifSkopeDisplayRole; + int namerole = (index.isValid() && index.column() == 0) ? Qt::DisplayRole : NifSkopeDisplayRole; QString text = index.data( namerole ).toString(); QString deco = index.data( Qt::DecorationRole ).toString(); diff --git a/src/nifmodel.cpp b/src/nifmodel.cpp index 0658fe7ec..b9a7811be 100644 --- a/src/nifmodel.cpp +++ b/src/nifmodel.cpp @@ -1726,8 +1726,8 @@ bool NifModel::setHeaderString( const QString & s ) bool NifModel::load( QIODevice & device ) { - QSettings cfg; - bool ignoreSize = cfg.value( "Ignore Block Size", true ).toBool(); + QSettings settings; + bool ignoreSize = settings.value( "Ignore Block Size", true ).toBool(); clear(); @@ -1737,8 +1737,7 @@ bool NifModel::load( QIODevice & device ) setState( Loading ); // read header - NifItem * header = nullptr; - header = getHeaderItem(); + NifItem * header = getHeaderItem(); if ( !header || !loadHeader( header, stream ) ) { auto m = tr( "failed to load file header (version %1, %2)" ).arg( version, 0, 16 ).arg( version2string( version ) ); if ( msgMode == UserMessage ) { @@ -1861,7 +1860,7 @@ bool NifModel::load( QIODevice & device ) throw tr( "encountered unknown block (%1)" ).arg( blktyp ); } } - catch ( QString err ) + catch ( QString & err ) { // version 20.3.0.3 can mostly recover from some failures because it store block sizes // XXX FIXME: if isNiBlock returned false, block numbering will be screwed up!! @@ -1956,7 +1955,7 @@ bool NifModel::load( QIODevice & device ) } } } - catch ( QString err ) { + catch ( QString & err ) { //If this is an old file we should still map the links, even if it failed mapLinks( linkMap ); @@ -1968,7 +1967,7 @@ bool NifModel::load( QIODevice & device ) mapLinks( linkMap ); } } - catch ( QString err ) + catch ( QString & err ) { if ( msgMode == UserMessage ) { Message::critical( nullptr, tr( "The NIF file could not be read. See Details for more information." ), err ); @@ -2112,7 +2111,7 @@ bool NifModel::loadHeaderOnly( const QString & fname ) return true; } -bool NifModel::earlyRejection( const QString & filepath, const QString & blockId, quint32 version ) +bool NifModel::earlyRejection( const QString & filepath, const QString & blockId, quint32 v ) { NifModel nif; @@ -2123,15 +2122,15 @@ bool NifModel::earlyRejection( const QString & filepath, const QString & blockId bool ver_match = false; - if ( version == 0 ) { + if ( v == 0 ) { ver_match = true; - } else if ( version != 0 && nif.getVersionNumber() == version ) { + } else if ( v != 0 && nif.getVersionNumber() == v ) { ver_match = true; } bool blk_match = false; - if ( blockId.isEmpty() == true || version < 0x0A000100 ) { + if ( blockId.isEmpty() == true || v < 0x0A000100 ) { blk_match = true; } else { const auto & types = nif.getArray( nif.getHeader(), "Block Types" ); diff --git a/src/nifmodel.h b/src/nifmodel.h index ecad327c7..8590128b9 100644 --- a/src/nifmodel.h +++ b/src/nifmodel.h @@ -278,7 +278,7 @@ class NifModel final : public BaseModel static QAbstractItemDelegate * createDelegate( QObject * parent, SpellBookPtr book ); //! Undo Stack for changes to NifModel - QUndoStack * undoStack; + QUndoStack * undoStack = nullptr; public slots: void updateSettings(); diff --git a/src/nifskope.cpp b/src/nifskope.cpp index 9c316cadb..218859617 100644 --- a/src/nifskope.cpp +++ b/src/nifskope.cpp @@ -739,7 +739,7 @@ QByteArray fileChecksum( const QString &fileName, QCryptographicHash::Algorithm return QByteArray(); } -void NifSkope::checkFile( QFileInfo fInfo, QByteArray filehash ) +void NifSkope::checkFile( QFileInfo fInfo, QByteArray hash ) { QString fname = fInfo.fileName(); QString fpath = fInfo.filePath(); @@ -753,7 +753,7 @@ void NifSkope::checkFile( QFileInfo fInfo, QByteArray filehash ) if ( saved ) { auto filehash2 = fileChecksum( tmpFile, QCryptographicHash::Md5 ); - if ( filehash == filehash2 ) { + if ( hash == filehash2 ) { tmp.remove( fname ); } else { QString err = "An MD5 hash comparison indicates this file will not be 100% identical upon saving. This could indicate underlying issues with the data in this file."; @@ -1378,7 +1378,7 @@ int main( int argc, char * argv[] ) void NifSkope::migrateSettings() const { // Load current NifSkope settings - QSettings cfg; + QSettings settings; // Load pre-1.2 NifSkope settings QSettings cfg1_1( "NifTools", "NifSkope" ); // Load NifSkope 1.2 settings @@ -1390,21 +1390,21 @@ void NifSkope::migrateSettings() const QString curDisplayVer = NifSkopeVersion::rawToDisplay( NIFSKOPE_VERSION, true ); // New Install, no need to migrate anything - if ( !cfg.value( "Version" ).isValid() && !cfg1_1.value( "version" ).isValid() ) { + if ( !settings.value( "Version" ).isValid() && !cfg1_1.value( "version" ).isValid() ) { // QSettings constructor creates an empty folder, so clear it. cfg1_1.clear(); // Set version values - cfg.setValue( "Version", curVer ); - cfg.setValue( "Qt Version", curQtVer ); - cfg.setValue( "Display Version", curDisplayVer ); + settings.setValue( "Version", curVer ); + settings.setValue( "Qt Version", curQtVer ); + settings.setValue( "Display Version", curDisplayVer ); return; } QString prevVer = curVer; - QString prevQtVer = cfg.value( "Qt Version" ).toString(); - QString prevDisplayVer = cfg.value( "Display Version" ).toString(); + QString prevQtVer = settings.value( "Qt Version" ).toString(); + QString prevDisplayVer = settings.value( "Display Version" ).toString(); // Set full granularity for version comparisons NifSkopeVersion::setNumParts( 7 ); @@ -1442,7 +1442,7 @@ void NifSkope::migrateSettings() const bool migrateFrom1_2 = testMigration( cfg1_2, "2.0" ); if ( !migrateFrom1_1 && !migrateFrom1_2 ) { - prevVer = cfg.value( "Version" ).toString(); + prevVer = settings.value( "Version" ).toString(); } NifSkopeVersion oldVersion( prevVer ); @@ -1462,14 +1462,14 @@ void NifSkope::migrateSettings() const // Migrate from 1.2.x to 2.0 if ( migrateFrom1_2 ) { qDebug() << "Migrating from 1.2 to 2.0"; - migrate( cfg1_2, cfg, migrateTo2_0 ); + migrate( cfg1_2, settings, migrateTo2_0 ); } // Set new Version - cfg.setValue( "Version", curVer ); + settings.setValue( "Version", curVer ); if ( prevDisplayVer != curDisplayVer ) - cfg.setValue( "Display Version", curDisplayVer ); + settings.setValue( "Display Version", curDisplayVer ); // Migrate to new Settings if ( oldVersion <= NifSkopeVersion( "2.0.dev1" ) ) { @@ -1490,19 +1490,19 @@ void NifSkope::migrateSettings() const return sanitized; }; - QVariant foldersVal = cfg.value( "Settings/Resources/Folders" ); + QVariant foldersVal = settings.value( "Settings/Resources/Folders" ); if ( foldersVal.toStringList().isEmpty() ) { - QVariant oldVal = cfg.value( "Render Settings/Texture Folders" ); + QVariant oldVal = settings.value( "Render Settings/Texture Folders" ); if ( !oldVal.isNull() ) { - cfg.setValue( "Settings/Resources/Folders", sanitize( oldVal ) ); + settings.setValue( "Settings/Resources/Folders", sanitize( oldVal ) ); } } - QVariant archivesVal = cfg.value( "Settings/Resources/Archives" ); + QVariant archivesVal = settings.value( "Settings/Resources/Archives" ); if ( archivesVal.toStringList().isEmpty() ) { - QVariant oldVal = cfg.value( "FSEngine/Archives" ); + QVariant oldVal = settings.value( "FSEngine/Archives" ); if ( !oldVal.isNull() ) { - cfg.setValue( "Settings/Resources/Archives", sanitize( oldVal ) ); + settings.setValue( "Settings/Resources/Archives", sanitize( oldVal ) ); } } @@ -1511,10 +1511,10 @@ void NifSkope::migrateSettings() const // Remove old keys - cfg.remove( "FSEngine" ); - cfg.remove( "Render Settings" ); - cfg.remove( "Settings/Language" ); - cfg.remove( "Settings/Startup Version" ); + settings.remove( "FSEngine" ); + settings.remove( "Render Settings" ); + settings.remove( "Settings/Language" ); + settings.remove( "Settings/Startup Version" ); } } @@ -1523,17 +1523,17 @@ void NifSkope::migrateSettings() const if ( curQtVer != prevQtVer ) { // Check all keys and delete all QByteArrays // to prevent portability problems between Qt versions - QStringList keys = cfg.allKeys(); + QStringList keys = settings.allKeys(); for ( const auto& key : keys ) { - if ( cfg.value( key ).type() == QVariant::ByteArray ) { + if ( settings.value( key ).type() == QVariant::ByteArray ) { qDebug() << "Removing Qt version-specific settings" << key << "while migrating settings from previous version"; - cfg.remove( key ); + settings.remove( key ); } } - cfg.setValue( "Qt Version", curQtVer ); + settings.setValue( "Qt Version", curQtVer ); } #endif } diff --git a/src/nifskope.h b/src/nifskope.h index 7008625df..7de2ba5f9 100644 --- a/src/nifskope.h +++ b/src/nifskope.h @@ -384,7 +384,7 @@ protected slots: bool isResizing; QTimer * resizeTimer; - QImage buf; + QImage viewBuffer; struct Settings { diff --git a/src/nifskope_ui.cpp b/src/nifskope_ui.cpp index ef7c3ecca..7977c5482 100644 --- a/src/nifskope_ui.cpp +++ b/src/nifskope_ui.cpp @@ -585,9 +585,9 @@ void NifSkope::initToolBars() // LOD Toolbar QToolBar * tLOD = ui->tLOD; - QSettings cfg; - int lodLevel = cfg.value( "GLView/LOD Level", 2 ).toInt(); - cfg.setValue( "GLView/LOD Level", lodLevel ); + QSettings settings; + int lodLevel = settings.value( "GLView/LOD Level", 2 ).toInt(); + settings.setValue( "GLView/LOD Level", lodLevel ); QSlider * lodSlider = new QSlider( Qt::Horizontal ); lodSlider->setFocusPolicy( Qt::StrongFocus ); @@ -1163,13 +1163,13 @@ bool NifSkope::eventFilter( QObject * o, QEvent * e ) ogl->getScene()->animate = false; ogl->updateGL(); - if ( buf.isNull() ) { + if ( viewBuffer.isNull() ) { // Init initial buffer with solid color // Otherwise becomes random colors on release builds - buf = QImage( 10, 10, QImage::Format_ARGB32 ); - buf.fill( ogl->clearColor() ); + viewBuffer = QImage( 10, 10, QImage::Format_ARGB32 ); + viewBuffer.fill( ogl->clearColor() ); } else { - buf = ogl->grabFrameBuffer(); + viewBuffer = ogl->grabFrameBuffer(); } ogl->setUpdatesEnabled( false ); @@ -1183,10 +1183,10 @@ bool NifSkope::eventFilter( QObject * o, QEvent * e ) } // Paint stored framebuffer over GLGraphicsView while resizing - if ( !buf.isNull() && isResizing && e->type() == QEvent::Paint ) { + if ( !viewBuffer.isNull() && isResizing && e->type() == QEvent::Paint ) { QPainter painter; painter.begin( graphicsView ); - painter.drawImage( QRect( 0, 0, painter.device()->width(), painter.device()->height() ), buf ); + painter.drawImage( QRect( 0, 0, painter.device()->width(), painter.device()->height() ), viewBuffer ); painter.end(); return true; diff --git a/src/niftypes.cpp b/src/niftypes.cpp index d7d034ef5..335d49e08 100644 --- a/src/niftypes.cpp +++ b/src/niftypes.cpp @@ -648,7 +648,7 @@ Transform operator*( const Transform & t1, const Transform & t2 ) bool Transform::canConstruct( const NifModel * nif, const QModelIndex & parent ) { - QModelIndex skinTransform = nif->getIndex( parent, "Skin Transform" ); + QModelIndex skinTransform = (nif) ? nif->getIndex( parent, "Skin Transform" ) : QModelIndex(); return nif && ( ( parent.isValid() && nif->getIndex( parent, "Rotation" ).isValid() diff --git a/src/niftypes.h b/src/niftypes.h index ac06fb064..e56c70f1e 100644 --- a/src/niftypes.h +++ b/src/niftypes.h @@ -1495,7 +1495,7 @@ class FixedMatrix { public: //! Default Constructor: Allocates empty vector - FixedMatrix() : v_( nullptr ), len0( 0 ), len1( 0 ) + FixedMatrix() {} /*! Size Constructor @@ -1622,9 +1622,9 @@ class FixedMatrix } private: - T * v_; //!< Vector data - int len0; //!< Length in first dimension - int len1; //!< Length in second dimension + T * v_ = nullptr; //!< Vector data + int len0 = 0; //!< Length in first dimension + int len1 = 0; //!< Length in second dimension }; typedef FixedMatrix ByteMatrix; diff --git a/src/nifvalue.h b/src/nifvalue.h index 797708d43..1e1a4c87b 100644 --- a/src/nifvalue.h +++ b/src/nifvalue.h @@ -357,7 +357,7 @@ class NifValue }; //! The data value. - Value val; + Value val = {0}; /*! Get the data as an object of type T. * @@ -431,16 +431,16 @@ class NifIStream final void init(); //! Whether a boolean is 32-bit. - bool bool32bit; + bool bool32bit = false; //! Whether link adjustment is required. - bool linkAdjust; + bool linkAdjust = false; //! Whether string adjustment is required. - bool stringAdjust; + bool stringAdjust = false; //! Whether the model is big-endian - bool bigEndian; + bool bigEndian = false; //! The maximum length of a string that can be read. - int maxLength; + int maxLength = 0x8000; }; @@ -465,13 +465,13 @@ class NifOStream final void init(); //! Whether a boolean is 32-bit. - bool bool32bit; + bool bool32bit = false; //! Whether link adjustment is required. - bool linkAdjust; + bool linkAdjust = false; //! Whether string adjustment is required. - bool stringAdjust; + bool stringAdjust = false; //! Whether the model is big-endian - bool bigEndian; + bool bigEndian = false; }; diff --git a/src/nifxml.cpp b/src/nifxml.cpp index 30901ead0..4f3458770 100644 --- a/src/nifxml.cpp +++ b/src/nifxml.cpp @@ -82,7 +82,6 @@ class NifXmlHandler final : public QXmlDefaultHandler //! Constructor NifXmlHandler() { - depth = 0; tags.insert( "niftoolsxml", tagFile ); tags.insert( "version", tagVersion ); tags.insert( "compound", tagCompound ); @@ -92,13 +91,12 @@ class NifXmlHandler final : public QXmlDefaultHandler tags.insert( "enum", tagEnum ); tags.insert( "option", tagOption ); tags.insert( "bitflags", tagBitFlag ); - blk = 0; } //! Current position on stack - int depth; + int depth = 0; //! Tag stack - Tag stack[10]; + Tag stack[10] = {}; //! Hashmap of tags QHash tags; //! Error string @@ -117,7 +115,7 @@ class NifXmlHandler final : public QXmlDefaultHandler QString optTxt; //! Block - NifBlockPtr blk; + NifBlockPtr blk = nullptr; //! Data NifData data; @@ -517,22 +515,22 @@ class NifXmlHandler final : public QXmlDefaultHandler } //! Checks that the type of the data is valid - bool checkType( const NifData & data ) + bool checkType( const NifData & d ) { - return ( NifModel::compounds.contains( data.type() ) - || NifValue::type( data.type() ) != NifValue::tNone - || data.type() == "TEMPLATE" + return ( NifModel::compounds.contains( d.type() ) + || NifValue::type( d.type() ) != NifValue::tNone + || d.type() == "TEMPLATE" ); } //! Checks that a template type is valid - bool checkTemp( const NifData & data ) + bool checkTemp( const NifData & d ) { - return ( data.temp().isEmpty() - || NifValue::type( data.temp() ) != NifValue::tNone - || data.temp() == "TEMPLATE" - || NifModel::blocks.contains( data.temp() ) - || NifModel::compounds.contains( data.temp() ) + return ( d.temp().isEmpty() + || NifValue::type( d.temp() ) != NifValue::tNone + || d.temp() == "TEMPLATE" + || NifModel::blocks.contains( d.temp() ) + || NifModel::compounds.contains( d.temp() ) ); } @@ -555,15 +553,15 @@ class NifXmlHandler final : public QXmlDefaultHandler } for ( const QString& key : NifModel::blocks.keys() ) { - NifBlockPtr blk = NifModel::blocks.value( key ); + NifBlockPtr b = NifModel::blocks.value( key ); - if ( !blk->ancestor.isEmpty() && !NifModel::blocks.contains( blk->ancestor ) ) - err( tr( "niobject %1 inherits unknown ancestor %2" ).arg( key, blk->ancestor ) ); + if ( !b->ancestor.isEmpty() && !NifModel::blocks.contains( b->ancestor ) ) + err( tr( "niobject %1 inherits unknown ancestor %2" ).arg( key, b->ancestor ) ); - if ( blk->ancestor == key ) + if ( b->ancestor == key ) err( tr( "niobject %1 inherits itself" ).arg( key ) ); - for ( const NifData& data : blk->types ) { + for ( const NifData& data : b->types ) { if ( !checkType( data ) ) err( tr( "niobject %1 refers to unknown type %2" ).arg( key, data.type() ) ); diff --git a/src/spells/animation.cpp b/src/spells/animation.cpp index 6e053775f..5a8e054ff 100644 --- a/src/spells/animation.cpp +++ b/src/spells/animation.cpp @@ -169,7 +169,7 @@ class spAttachKf final : public Spell //return iRoot; } - catch ( QString e ) + catch ( QString & e ) { Message::append( Spell::tr( "Errors occurred while attaching .KF" ), e ); } diff --git a/src/spells/flags.cpp b/src/spells/flags.cpp index c4950d9b8..caaeb8b43 100644 --- a/src/spells/flags.cpp +++ b/src/spells/flags.cpp @@ -701,7 +701,7 @@ class spEditFlags final : public Spell dlgButtons( &dlg, vbox ); - if ( dlg.exec() && QDialog::Accepted ) { + if ( dlg.exec() == QDialog::Accepted ) { if ( nif->checkVersion( 0, 0x14000005 ) ) { nif->set( nif->getBlock( index ), "Stencil Enabled", chkEnable->isChecked() ); nif->set( nif->getBlock( index ), "Fail Action", cmbFail->currentIndex() ); @@ -764,7 +764,7 @@ class spEditFlags final : public Spell dlgButtons( &dlg, vbox ); - if ( dlg.exec() && QDialog::Accepted ) { + if ( dlg.exec() == QDialog::Accepted ) { if ( nif->checkVersion( 0, 0x14000005 ) ) { nif->set( nif->getBlock( index ), "Lighting Mode", cmbLight->currentIndex() ); nif->set( nif->getBlock( index ), "Vertex Mode", cmbVert->currentIndex() ); diff --git a/src/spells/mesh.cpp b/src/spells/mesh.cpp index b1a45ac2c..1336185fb 100644 --- a/src/spells/mesh.cpp +++ b/src/spells/mesh.cpp @@ -230,7 +230,7 @@ static void removeWasteVertices( NifModel * nif, const QModelIndex & iData, cons Message::warning( nullptr, Spell::tr( "The skin partition was removed, please regenerate it with the skin partition spell" ) ); } } - catch ( QString e ) + catch ( QString & e ) { Message::warning( nullptr, Spell::tr( "There were errors during the operation" ), e ); } @@ -572,7 +572,7 @@ class spRemoveDuplicateVertices final : public Spell removeWasteVertices( nif, iData, iShape ); } - catch ( QString e ) + catch ( QString & e ) { Message::warning( nullptr, Spell::tr( "There were errors during the operation" ), e ); } diff --git a/src/spells/moppcode.cpp b/src/spells/moppcode.cpp index 7e2cce2fa..442f20e2f 100644 --- a/src/spells/moppcode.cpp +++ b/src/spells/moppcode.cpp @@ -250,7 +250,7 @@ class spAllMoppCodes final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & idx ) override final { - if ( nif->getUserVersion() != 10 && nif->getUserVersion() != 11 ) + if ( nif && nif->getUserVersion() != 10 && nif->getUserVersion() != 11 ) return false; if ( TheHavokCode.Initialize() ) { diff --git a/src/spells/skeleton.cpp b/src/spells/skeleton.cpp index 3f02a01c1..56d2a247f 100644 --- a/src/spells/skeleton.cpp +++ b/src/spells/skeleton.cpp @@ -969,7 +969,7 @@ class spSkinPartition final : public Spell return iShape; } - catch ( QString err ) + catch ( QString & err ) { if ( !err.isEmpty() ) QMessageBox::warning( 0, "NifSkope", err ); diff --git a/src/spells/texture.cpp b/src/spells/texture.cpp index ff7ff2360..24f19c19e 100644 --- a/src/spells/texture.cpp +++ b/src/spells/texture.cpp @@ -985,9 +985,9 @@ class spEmbedTexture final : public Spell REGISTER_SPELL( spEmbedTexture ) -TexFlipDialog::TexFlipDialog( NifModel * nif, QModelIndex & index, QWidget * parent ) : QDialog( parent ) +TexFlipDialog::TexFlipDialog( NifModel * n, QModelIndex & index, QWidget * parent ) : QDialog( parent ) { - this->nif = nif; + nif = n; baseIndex = index; grid = new QGridLayout; diff --git a/src/widgets/colorwheel.cpp b/src/widgets/colorwheel.cpp index 5005e278e..87cfd904d 100644 --- a/src/widgets/colorwheel.cpp +++ b/src/widgets/colorwheel.cpp @@ -118,7 +118,7 @@ ColorWheel::ColorWheel( const QColor & c, QWidget * parent ) : QWidget( parent ) if ( S > 1.0 || S < 0.0 ) S = 1.0; - if ( V > 1.0 || S < 0.0 ) + if ( V > 1.0 || V < 0.0 ) V = 1.0; } @@ -148,7 +148,7 @@ void ColorWheel::setColor( const QColor & c ) if ( S > 1.0 || S < 0.0 ) S = 1.0; - if ( V > 1.0 || S < 0.0 ) + if ( V > 1.0 || V < 0.0 ) V = 1.0; H = h; @@ -199,8 +199,8 @@ int ColorWheel::heightForWidth( int width ) const return width; } -#ifndef pi -#define pi 3.1416 +#ifndef M_PI +#define M_PI 3.1415926535897932385 #endif void ColorWheel::paintEvent( QPaintEvent * e ) @@ -229,12 +229,12 @@ void ColorWheel::paintEvent( QPaintEvent * e ) p.setBrush( palette().color( QPalette::Background ) ); p.drawEllipse( QRectF( -c, -c, c * 2, c * 2 ) ); - double x = ( H - 0.5 ) * 2 * pi; + double x = ( H - 0.5 ) * 2 * M_PI; QPointF points[3]; points[0] = QPointF( sin( x ) * c, cos( x ) * c ); - points[1] = QPointF( sin( x + 2 * pi / 3 ) * c, cos( x + 2 * pi / 3 ) * c ); - points[2] = QPointF( sin( x + 4 * pi / 3 ) * c, cos( x + 4 * pi / 3 ) * c ); + points[1] = QPointF( sin( x + 2 * M_PI / 3 ) * c, cos( x + 2 * M_PI / 3 ) * c ); + points[2] = QPointF( sin( x + 4 * M_PI / 3 ) * c, cos( x + 4 * M_PI / 3 ) * c ); QColor colors[3][2]; colors[0][0] = QColor::fromHsvF( H, 1.0, 1.0, 1.0 ); @@ -288,7 +288,7 @@ void ColorWheel::mousePressEvent( QMouseEvent * e ) double dy = abs( e->y() - height() / 2 ); double d = sqrt( dx * dx + dy * dy ); - double s = qMin( width(), height() ) / 2; + double s = qMin( width(), height() ) / 2.0; double c = s - s / 5; if ( d > s ) @@ -356,7 +356,7 @@ void ColorWheel::setColor( int x, int y ) QMatrix m; m.rotate( (H ) * 360.0 + 120 ); QPointF p( m.map( mp ) ); - double c = qMin( width(), height() ) / 2; + double c = qMin( width(), height() ) / 2.0; c -= c / 5; V = ( p.y() + c ) / ( c + c / 2 ); @@ -364,7 +364,7 @@ void ColorWheel::setColor( int x, int y ) if ( V > 1.0 ) V = 1.0; if ( V > 0 ) { - double h = V * ( c + c / 2 ) / ( 2.0 * sin( 60.0 / 180.0 * pi ) ); + double h = V * ( c + c / 2 ) / ( 2.0 * sin( 60.0 / 180.0 * M_PI ) ); S = ( p.x() + h ) / ( h * 2 ); if ( S < 0.0 ) S = 0.0; diff --git a/src/widgets/colorwheel.h b/src/widgets/colorwheel.h index d97725d2e..c0b330e0e 100644 --- a/src/widgets/colorwheel.h +++ b/src/widgets/colorwheel.h @@ -92,14 +92,14 @@ public slots: void setColor( int x, int y ); private: - double H, S, V, A; + double H = 0, S = 0, V = 0, A = 0; - bool isAlpha; + bool isAlpha = false; enum { Nope, Circle, Triangle - } pressed; + } pressed = Nope; QSize sHint; }; diff --git a/src/widgets/fileselect.cpp b/src/widgets/fileselect.cpp index 4ef610e82..48d26aa71 100644 --- a/src/widgets/fileselect.cpp +++ b/src/widgets/fileselect.cpp @@ -191,9 +191,9 @@ void FileSelector::replaceText( const QString & x ) line->setCompleter( completer ); } -void FileSelector::setFilter( const QStringList & fltr ) +void FileSelector::setFilter( const QStringList & f ) { - this->fltr = fltr; + fltr = f; if ( dirmdl ) dirmdl->setNameFilters( fltr ); diff --git a/src/widgets/floatedit.cpp b/src/widgets/floatedit.cpp index 17508e4a9..fd99c9dcc 100644 --- a/src/widgets/floatedit.cpp +++ b/src/widgets/floatedit.cpp @@ -172,5 +172,5 @@ bool FloatEdit::isMin() const bool FloatEdit::isMax() const { - return ( text() == tr( "" ) ); + return ( text() == tr( "" ) ); } diff --git a/src/widgets/nifeditors.cpp b/src/widgets/nifeditors.cpp index fea049040..ba584a788 100644 --- a/src/widgets/nifeditors.cpp +++ b/src/widgets/nifeditors.cpp @@ -186,26 +186,26 @@ void NifEditBox::sltApplyData() } -NifFloatSlider::NifFloatSlider( NifModel * nif, const QModelIndex & index, float min, float max ) - : NifEditBox( nif, index ) +NifFloatSlider::NifFloatSlider( NifModel * n, const QModelIndex & index, float min, float max ) + : NifEditBox( n, index ) { getLayout()->addWidget( slider = new FloatSlider( Qt::Horizontal, true, false ) ); slider->setRange( min, max ); connect( slider, &FloatSlider::valueChanged, this, &NifFloatSlider::sltApplyData ); } -void NifFloatSlider::updateData( NifModel * nif ) +void NifFloatSlider::updateData( NifModel * n ) { - slider->setValue( nif->get( index ) ); + slider->setValue( n->get( index ) ); } -void NifFloatSlider::applyData( NifModel * nif ) +void NifFloatSlider::applyData( NifModel * n ) { - nif->set( index, slider->value() ); + n->set( index, slider->value() ); } -NifFloatEdit::NifFloatEdit( NifModel * nif, const QModelIndex & index, float min, float max ) - : NifEditBox( nif, index ) +NifFloatEdit::NifFloatEdit( NifModel * n, const QModelIndex & index, float min, float max ) + : NifEditBox( n, index ) { getLayout()->addWidget( spinbox = new QDoubleSpinBox() ); spinbox->setRange( min, max ); @@ -216,42 +216,42 @@ NifFloatEdit::NifFloatEdit( NifModel * nif, const QModelIndex & index, float min connect( spinbox, dsbValueChanged, this, &NifFloatEdit::sltApplyData ); } -void NifFloatEdit::updateData( NifModel * nif ) +void NifFloatEdit::updateData( NifModel * n ) { - spinbox->setValue( nif->get( index ) ); + spinbox->setValue( n->get( index ) ); } -void NifFloatEdit::applyData( NifModel * nif ) +void NifFloatEdit::applyData( NifModel * n ) { - nif->set( index, spinbox->value() ); + n->set( index, spinbox->value() ); } -NifLineEdit::NifLineEdit( NifModel * nif, const QModelIndex & index ) - : NifEditBox( nif, index ) +NifLineEdit::NifLineEdit( NifModel * n, const QModelIndex & index ) + : NifEditBox( n, index ) { getLayout()->addWidget( line = new QLineEdit() ); connect( line, &QLineEdit::textEdited, this, &NifLineEdit::sltApplyData ); } -void NifLineEdit::updateData( NifModel * nif ) +void NifLineEdit::updateData( NifModel * n ) { - line->setText( nif->get( index ) ); + line->setText( n->get( index ) ); } -void NifLineEdit::applyData( NifModel * nif ) +void NifLineEdit::applyData( NifModel * n ) { - nif->set( index, line->text() ); + n->set( index, line->text() ); } -NifColorEdit::NifColorEdit( NifModel * nif, const QModelIndex & index ) - : NifEditBox( nif, index ) +NifColorEdit::NifColorEdit( NifModel * n, const QModelIndex & index ) + : NifEditBox( n, index ) { getLayout()->addWidget( color = new ColorWheel() ); color->setSizeHint( QSize( 140, 140 ) ); connect( color, &ColorWheel::sigColor, this, &NifColorEdit::sltApplyData ); - auto typ = nif->getValue( index ).type(); + auto typ = n->getValue( index ).type(); if ( typ == NifValue::tColor4 || typ == NifValue::tByteColor4 ) { getLayout()->addWidget( alpha = new AlphaSlider() ); connect( alpha, &AlphaSlider::valueChanged, this, &NifColorEdit::sltApplyData ); @@ -260,40 +260,40 @@ NifColorEdit::NifColorEdit( NifModel * nif, const QModelIndex & index ) } } -void NifColorEdit::updateData( NifModel * nif ) +void NifColorEdit::updateData( NifModel * n ) { if ( alpha ) { - color->setColor( nif->get( index ).toQColor() ); - alpha->setValue( nif->get( index )[3] ); + color->setColor( n->get( index ).toQColor() ); + alpha->setValue( n->get( index )[3] ); } else { - color->setColor( nif->get( index ).toQColor() ); + color->setColor( n->get( index ).toQColor() ); } } -void NifColorEdit::applyData( NifModel * nif ) +void NifColorEdit::applyData( NifModel * n ) { if ( alpha ) { Color4 c4; c4.fromQColor( color->getColor() ); c4[3] = alpha->value(); - nif->set( index, c4 ); + n->set( index, c4 ); } else { Color3 c3; c3.fromQColor( color->getColor() ); - nif->set( index, c3 ); + n->set( index, c3 ); } } -NifVectorEdit::NifVectorEdit( NifModel * nif, const QModelIndex & index ) - : NifEditBox( nif, index ) +NifVectorEdit::NifVectorEdit( NifModel * n, const QModelIndex & index ) + : NifEditBox( n, index ) { getLayout()->addWidget( vector = new VectorEdit() ); connect( vector, &VectorEdit::sigEdited, this, &NifVectorEdit::sltApplyData ); } -void NifVectorEdit::updateData( NifModel * nif ) +void NifVectorEdit::updateData( NifModel * n ) { - NifValue val = nif->getValue( index ); + NifValue val = n->getValue( index ); if ( val.type() == NifValue::tVector3 ) vector->setVector3( val.get() ); @@ -301,26 +301,26 @@ void NifVectorEdit::updateData( NifModel * nif ) vector->setVector2( val.get() ); } -void NifVectorEdit::applyData( NifModel * nif ) +void NifVectorEdit::applyData( NifModel * n ) { - NifValue::Type type = nif->getValue( index ).type(); + NifValue::Type type = n->getValue( index ).type(); if ( type == NifValue::tVector3 ) - nif->set( index, vector->getVector3() ); + n->set( index, vector->getVector3() ); else if ( type == NifValue::tVector2 ) - nif->set( index, vector->getVector2() ); + n->set( index, vector->getVector2() ); } -NifRotationEdit::NifRotationEdit( NifModel * nif, const QModelIndex & index ) - : NifEditBox( nif, index ) +NifRotationEdit::NifRotationEdit( NifModel * n, const QModelIndex & index ) + : NifEditBox( n, index ) { getLayout()->addWidget( rotation = new RotationEdit() ); connect( rotation, &RotationEdit::sigEdited, this, &NifRotationEdit::sltApplyData ); } -void NifRotationEdit::updateData( NifModel * nif ) +void NifRotationEdit::updateData( NifModel * n ) { - NifValue val = nif->getValue( index ); + NifValue val = n->getValue( index ); if ( val.type() == NifValue::tMatrix ) rotation->setMatrix( val.get() ); @@ -328,18 +328,18 @@ void NifRotationEdit::updateData( NifModel * nif ) rotation->setQuat( val.get() ); } -void NifRotationEdit::applyData( NifModel * nif ) +void NifRotationEdit::applyData( NifModel * n ) { - NifValue::Type type = nif->getValue( index ).type(); + NifValue::Type type = n->getValue( index ).type(); if ( type == NifValue::tMatrix ) - nif->set( index, rotation->getMatrix() ); + n->set( index, rotation->getMatrix() ); else if ( type == NifValue::tQuat || type == NifValue::tQuatXYZW ) - nif->set( index, rotation->getQuat() ); + n->set( index, rotation->getQuat() ); } -NifMatrix4Edit::NifMatrix4Edit( NifModel * nif, const QModelIndex & index ) - : NifEditBox( nif, index ), setting( false ) +NifMatrix4Edit::NifMatrix4Edit( NifModel * n, const QModelIndex & index ) + : NifEditBox( n, index ) { QBoxLayout * vbox = new QVBoxLayout; setLayout( vbox ); @@ -366,12 +366,12 @@ NifMatrix4Edit::NifMatrix4Edit( NifModel * nif, const QModelIndex & index ) connect( scale, &VectorEdit::sigEdited, this, &NifMatrix4Edit::sltApplyData ); } -void NifMatrix4Edit::updateData( NifModel * nif ) +void NifMatrix4Edit::updateData( NifModel * n ) { if ( setting ) return; - Matrix4 mtx = nif->get( index ); + Matrix4 mtx = n->get( index ); Vector3 t, s; Matrix r; @@ -383,12 +383,12 @@ void NifMatrix4Edit::updateData( NifModel * nif ) scale->setVector3( s ); } -void NifMatrix4Edit::applyData( NifModel * nif ) +void NifMatrix4Edit::applyData( NifModel * n ) { setting = true; Matrix4 mtx; mtx.compose( translation->getVector3(), rotation->getMatrix(), scale->getVector3() ); - nif->set( index, mtx ); + n->set( index, mtx ); setting = false; } diff --git a/src/widgets/nifeditors.h b/src/widgets/nifeditors.h index 76c79807c..65f3f351b 100644 --- a/src/widgets/nifeditors.h +++ b/src/widgets/nifeditors.h @@ -195,7 +195,7 @@ class NifMatrix4Edit final : public NifEditBox class RotationEdit * rotation; class VectorEdit * scale; - bool setting; + bool setting = false; }; #endif diff --git a/src/widgets/nifview.cpp b/src/widgets/nifview.cpp index 9b7a9b577..9d90faf5c 100644 --- a/src/widgets/nifview.cpp +++ b/src/widgets/nifview.cpp @@ -273,6 +273,8 @@ auto splitMime = []( QString format ) { if ( split.value( 0 ) == "nifskope" && (split.value( 1 ) == "niblock" || split.value( 1 ) == "nibranch") ) return !split.value( 2 ).isEmpty(); + + return false; }; void NifTreeView::keyPressEvent( QKeyEvent * e ) diff --git a/src/widgets/uvedit.cpp b/src/widgets/uvedit.cpp index 80ffe6e93..19aef8d17 100644 --- a/src/widgets/uvedit.cpp +++ b/src/widgets/uvedit.cpp @@ -923,10 +923,10 @@ bool UVWidget::setNifData( NifModel * nifModel, const QModelIndex & nifIndex ) // Assume that a FO3 mesh never has embedded textures... //texsource = iTexSource; //return true; - QModelIndex textures = nif->getIndex( iTexSource, "Textures" ); + QModelIndex iTextures = nif->getIndex( iTexSource, "Textures" ); - if ( textures.isValid() ) { - texfile = TexCache::find( nif->get( textures.child( 0, 0 ) ), nif->getFolder() ); + if ( iTextures.isValid() ) { + texfile = TexCache::find( nif->get( iTextures.child( 0, 0 ) ), nif->getFolder() ); return true; } } @@ -1089,26 +1089,26 @@ class UVWSelectCommand final : public QUndoCommand void UVWidget::select( int index, bool yes ) { - QList selection = this->selection; + QList sel = this->selection; if ( yes ) { - if ( !selection.contains( index ) ) - selection.append( index ); + if ( !sel.contains( index ) ) + sel.append( index ); } else { - selection.removeAll( index ); + sel.removeAll( index ); } - undoStack->push( new UVWSelectCommand( this, selection ) ); + undoStack->push( new UVWSelectCommand( this, sel ) ); } void UVWidget::select( const QRegion & r, bool add ) { - QList selection( add ? this->selection : QList() ); + QList sel( add ? this->selection : QList() ); for ( const auto s : indices( r ) ) { - if ( !selection.contains( s ) ) - selection.append( s ); + if ( !sel.contains( s ) ) + sel.append( s ); } - undoStack->push( new UVWSelectCommand( this, selection ) ); + undoStack->push( new UVWSelectCommand( this, sel ) ); } void UVWidget::selectNone() @@ -1118,40 +1118,40 @@ void UVWidget::selectNone() void UVWidget::selectAll() { - QList selection; + QList sel; for ( int s = 0; s < texcoords.count(); s++ ) - selection << s; + sel << s; - undoStack->push( new UVWSelectCommand( this, selection ) ); + undoStack->push( new UVWSelectCommand( this, sel ) ); } void UVWidget::selectFaces() { - QList selection = this->selection; - for ( const auto s : QList( selection ) ) { + QList sel = this->selection; + for ( const auto s : QList( sel ) ) { for ( const auto f : texcoords2faces.values( s ) ) { for ( int i = 0; i < 3; i++ ) { - if ( !selection.contains( faces[f].tc[i] ) ) - selection.append( faces[f].tc[i] ); + if ( !sel.contains( faces[f].tc[i] ) ) + sel.append( faces[f].tc[i] ); } } } - undoStack->push( new UVWSelectCommand( this, selection ) ); + undoStack->push( new UVWSelectCommand( this, sel ) ); } void UVWidget::selectConnected() { - QList selection = this->selection; + QList sel = this->selection; bool more = true; while ( more ) { more = false; - for ( const auto s : QList( selection ) ) { + for ( const auto s : QList( sel ) ) { for ( const auto f :texcoords2faces.values( s ) ) { for ( int i = 0; i < 3; i++ ) { - if ( !selection.contains( faces[f].tc[i] ) ) { - selection.append( faces[f].tc[i] ); + if ( !sel.contains( faces[f].tc[i] ) ) { + sel.append( faces[f].tc[i] ); more = true; } } @@ -1159,7 +1159,7 @@ void UVWidget::selectConnected() } } - undoStack->push( new UVWSelectCommand( this, selection ) ); + undoStack->push( new UVWSelectCommand( this, sel ) ); } class UVWMoveCommand final : public QUndoCommand diff --git a/src/widgets/uvedit.h b/src/widgets/uvedit.h index 81a6409f5..606defb94 100644 --- a/src/widgets/uvedit.h +++ b/src/widgets/uvedit.h @@ -152,13 +152,13 @@ protected slots: //! A UV face struct face { - int index; + int index = -1; - int tc[3]; + int tc[3] = {0}; bool contains( int v ) { return ( tc[0] == v || tc[1] == v || tc[2] == v ); } - face() : index( -1 ) {} + face() {} face( int idx, int tc1, int tc2, int tc3 ) : index( idx ) { tc[0] = tc1; tc[1] = tc2; tc[2] = tc3; } }; diff --git a/src/widgets/xmlcheck.cpp b/src/widgets/xmlcheck.cpp index b5489e2f8..484d6f1d5 100644 --- a/src/widgets/xmlcheck.cpp +++ b/src/widgets/xmlcheck.cpp @@ -298,7 +298,7 @@ void TestShredder::closeEvent( QCloseEvent * e ) QQueue FileQueue::make( const QString & dname, const QStringList & extensions, bool recursive ) { - QQueue queue; + QQueue paths; QDir dir( dname ); @@ -306,25 +306,25 @@ QQueue FileQueue::make( const QString & dname, const QStringList & exte dir.setFilter( QDir::Dirs ); for ( const QString& d : dir.entryList() ) { if ( d != "." && d != ".." ) - queue += make( dir.filePath( d ), extensions, true ); + paths += make( dir.filePath( d ), extensions, true ); } } dir.setFilter( QDir::Files ); dir.setNameFilters( extensions ); for ( const QString& f : dir.entryList() ) { - queue.enqueue( dir.filePath( f ) ); + paths.enqueue( dir.filePath( f ) ); } - return queue; + return paths; } void FileQueue::init( const QString & dname, const QStringList & extensions, bool recursive ) { - QQueue queue = make( dname, extensions, recursive ); + QQueue paths = make( dname, extensions, recursive ); mutex.lock(); - this->queue = queue; + this->queue = paths; mutex.unlock(); } diff --git a/src/widgets/xmlcheck.h b/src/widgets/xmlcheck.h index bf474e55a..d559a8e64 100644 --- a/src/widgets/xmlcheck.h +++ b/src/widgets/xmlcheck.h @@ -53,8 +53,8 @@ class TestThread final : public QThread ~TestThread(); QString blockMatch; - quint32 verMatch; - bool reportAll; + quint32 verMatch = 0; + bool reportAll = false; signals: void sigStart( const QString & file ); From d656dfcb991e6033afc00696b41685349fc9236d Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Fri, 23 Jun 2017 06:49:07 -0400 Subject: [PATCH 035/152] Static Analysis optimizations Mostly converting arguments to const reference. --- lib/fsengine/bsa.cpp | 22 ++++++++++++---------- src/basemodel.cpp | 2 -- src/gl/controllers.cpp | 17 +++++++++-------- src/gl/glnode.cpp | 1 - src/gl/glparticles.cpp | 3 --- src/gl/glproperty.cpp | 8 ++++---- src/gl/glproperty.h | 8 ++++---- src/gl/gltools.cpp | 26 +++++++++++++------------- src/gl/gltools.h | 26 +++++++++++++------------- src/glview.cpp | 7 ++++--- src/glview.h | 2 +- src/importex/3ds.cpp | 2 +- src/importex/obj.cpp | 2 +- src/nifmodel.cpp | 32 ++++++++++++++------------------ src/nifskope.cpp | 8 ++++---- src/nifskope_ui.cpp | 2 +- src/niftypes.h | 18 +++++++++--------- src/spells/blocks.cpp | 6 +++--- src/spells/optimize.cpp | 8 ++++---- src/spells/sanitize.cpp | 3 +-- src/spells/stringpalette.cpp | 2 +- src/widgets/nifview.h | 2 +- src/widgets/uvedit.cpp | 2 +- 23 files changed, 101 insertions(+), 108 deletions(-) diff --git a/lib/fsengine/bsa.cpp b/lib/fsengine/bsa.cpp index 04d7b2321..a2fb1b1d1 100644 --- a/lib/fsengine/bsa.cpp +++ b/lib/fsengine/bsa.cpp @@ -760,10 +760,12 @@ bool BSA::scan( const BSA::BSAFolder * folder, QStandardItem * item, QString pat if ( !folder || folder->children.count() == 0 ) return false; + auto children = folder->children; QHash::const_iterator i; - for ( i = folder->children.begin(); i != folder->children.end(); i++ ) { - - if ( !i.value()->files.count() && !i.value()->children.count() ) + for ( i = children.begin(); i != children.end(); ++i ) { + auto f = i.value()->files; + auto c = i.value()->children; + if ( !f.count() && !c.count() ) continue; auto folderItem = new QStandardItem( i.key() ); @@ -773,21 +775,21 @@ bool BSA::scan( const BSA::BSAFolder * folder, QStandardItem * item, QString pat item->appendRow( { folderItem, pathDummy, sizeDummy } ); // Recurse through folders - if ( i.value()->children.count() ) { + if ( c.count() ) { QString fullpath = ((path.isEmpty()) ? path : path % "/") % i.key(); scan( i.value(), folderItem, fullpath ); } // List files - if ( i.value()->files.count() ) { - QHash::const_iterator f; - for ( f = i.value()->files.begin(); f != i.value()->files.end(); f++ ) { - QString fullpath = path % "/" % i.key() % "/" % f.key(); + if ( f.count() ) { + QHash::const_iterator it; + for ( it = f.begin(); it != f.end(); ++it ) { + QString fullpath = path % "/" % i.key() % "/" % it.key(); - int bytes = f.value()->size(); + int bytes = it.value()->size(); QString filesize = (bytes > 1024) ? QString::number( bytes / 1024 ) + "KB" : QString::number( bytes ) + "B"; - auto fileItem = new QStandardItem( f.key() ); + auto fileItem = new QStandardItem( it.key() ); auto pathItem = new QStandardItem( fullpath ); auto sizeItem = new QStandardItem( filesize ); diff --git a/src/basemodel.cpp b/src/basemodel.cpp index 1330a4ae7..8965e62d0 100644 --- a/src/basemodel.cpp +++ b/src/basemodel.cpp @@ -417,8 +417,6 @@ QVariant BaseModel::data( const QModelIndex & index, int role ) const } case Qt::ToolTipRole: { - QString tip; - switch ( column ) { case ValueCol: { diff --git a/src/gl/controllers.cpp b/src/gl/controllers.cpp index a0a9ce681..a43f908a9 100644 --- a/src/gl/controllers.cpp +++ b/src/gl/controllers.cpp @@ -337,20 +337,21 @@ bool MultiTargetTransformController::setInterpolator( Node * node, const QModelI while ( it.hasNext() ) { it.next(); - if ( it.value().first == node ) { - if ( it.value().second ) { - delete it.value().second; - it.value().second = 0; + auto val = it.value(); + if ( val.first == node ) { + if ( val.second ) { + delete val.second; + val.second = 0; } if ( nif->isNiBlock( idx, "NiBSplineCompTransformInterpolator" ) ) { - it.value().second = new BSplineTransformInterpolator( this ); + val.second = new BSplineTransformInterpolator( this ); } else if ( nif->isNiBlock( idx, "NiTransformInterpolator" ) ) { - it.value().second = new TransformInterpolator( this ); + val.second = new TransformInterpolator( this ); } - if ( it.value().second ) { - it.value().second->update( nif, idx ); + if ( val.second ) { + val.second->update( nif, idx ); } return true; diff --git a/src/gl/glnode.cpp b/src/gl/glnode.cpp index d0e6aabc1..8fbfe6c95 100644 --- a/src/gl/glnode.cpp +++ b/src/gl/glnode.cpp @@ -1604,7 +1604,6 @@ void Node::drawHavok() void drawFurnitureMarker( const NifModel * nif, const QModelIndex & iPosition ) { - QString name = nif->itemName( iPosition ); Vector3 offs = nif->get( iPosition, "Offset" ); quint16 orient = nif->get( iPosition, "Orientation" ); quint8 ref1 = nif->get( iPosition, "Position Ref 1" ); diff --git a/src/gl/glparticles.cpp b/src/gl/glparticles.cpp index cab04963a..26aabe03b 100644 --- a/src/gl/glparticles.cpp +++ b/src/gl/glparticles.cpp @@ -68,9 +68,6 @@ void Particles::update( const NifModel * nif, const QModelIndex & index ) if ( !iChild.isValid() ) continue; - QString name = nif->itemName( iChild ); - - //if ( name == "NiParticlesData" || name == "NiRotatingParticlesData" || name == "NiAutoNormalParticlesData" ) if ( nif->inherits( iChild, "NiParticlesData" ) ) { iData = iChild; upData = true; diff --git a/src/gl/glproperty.cpp b/src/gl/glproperty.cpp index cd65e86f2..22444606e 100644 --- a/src/gl/glproperty.cpp +++ b/src/gl/glproperty.cpp @@ -1266,13 +1266,13 @@ ShaderFlags::ShaderType BSLightingShaderProperty::getShaderType() return shaderType; } -void BSLightingShaderProperty::setEmissive( Color3 color, float mult ) +void BSLightingShaderProperty::setEmissive( const Color3 & color, float mult ) { emissiveColor = color; emissiveMult = mult; } -void BSLightingShaderProperty::setSpecular( Color3 color, float gloss, float strength ) +void BSLightingShaderProperty::setSpecular( const Color3 & color, float gloss, float strength ) { specularColor = color; specularGloss = gloss; @@ -1390,7 +1390,7 @@ Color3 BSLightingShaderProperty::getTintColor() const return tintColor; } -void BSLightingShaderProperty::setTintColor( Color3 c ) +void BSLightingShaderProperty::setTintColor( const Color3 & c ) { tintColor = c; } @@ -1524,7 +1524,7 @@ void BSEffectShaderProperty::setController( const NifModel * nif, const QModelIn } } -void BSEffectShaderProperty::setEmissive( Color4 color, float mult ) +void BSEffectShaderProperty::setEmissive( const Color4 & color, float mult ) { emissiveColor = color; emissiveMult = mult; diff --git a/src/gl/glproperty.h b/src/gl/glproperty.h index 241f9376d..43c8c5661 100644 --- a/src/gl/glproperty.h +++ b/src/gl/glproperty.h @@ -629,8 +629,8 @@ class BSLightingShaderProperty final : public BSShaderLightingProperty void setShaderType( unsigned int ); - void setEmissive( Color3 color, float mult = 1.0f ); - void setSpecular( Color3 color, float glossiness = 80.0f, float strength = 1.0f ); + void setEmissive( const Color3 & color, float mult = 1.0f ); + void setSpecular( const Color3 & color, float glossiness = 80.0f, float strength = 1.0f ); void setLightingEffect1( float ); void setLightingEffect2( float ); @@ -645,7 +645,7 @@ class BSLightingShaderProperty final : public BSShaderLightingProperty void setAlpha( float ); Color3 getTintColor() const; - void setTintColor( Color3 ); + void setTintColor( const Color3 & ); bool hasVertexColors = false; bool hasVertexAlpha = false; @@ -724,7 +724,7 @@ class BSEffectShaderProperty final : public BSShaderLightingProperty float getAlpha() const; - void setEmissive( Color4 color, float mult = 1.0f ); + void setEmissive( const Color4 & color, float mult = 1.0f ); void setFalloff( float, float, float, float, float ); float getEnvironmentReflection() const; diff --git a/src/gl/gltools.cpp b/src/gl/gltools.cpp index 84a41a196..8299ce41b 100644 --- a/src/gl/gltools.cpp +++ b/src/gl/gltools.cpp @@ -237,7 +237,7 @@ BoundSphere operator*( const Transform & t, const BoundSphere & sphere ) * draw primitives */ -void drawAxes( Vector3 c, float axis ) +void drawAxes( const Vector3 & c, float axis ) { glPushMatrix(); glTranslate( c ); @@ -299,7 +299,7 @@ QVector sortAxes( QVector axesDots ) return{ x, y, z }; } -void drawAxesOverlay( Vector3 c, float axis, QVector axesOrder ) +void drawAxesOverlay( const Vector3 & c, float axis, QVector axesOrder ) { glPushMatrix(); glTranslate( c ); @@ -368,7 +368,7 @@ void drawAxesOverlay( Vector3 c, float axis, QVector axesOrder ) glPopMatrix(); } -void drawBox( Vector3 a, Vector3 b ) +void drawBox( const Vector3 & a, const Vector3 & b ) { glBegin( GL_LINE_STRIP ); glVertex3f( a[0], a[1], a[2] ); @@ -425,14 +425,14 @@ void drawGrid( int s /* grid size */, int line /* line spacing */, int sub /* # glDisable( GL_BLEND ); } -void drawCircle( Vector3 c, Vector3 n, float r, int sd ) +void drawCircle( const Vector3 & c, const Vector3 & n, float r, int sd ) { Vector3 x = Vector3::crossproduct( n, Vector3( n[1], n[2], n[0] ) ); Vector3 y = Vector3::crossproduct( n, x ); drawArc( c, x * r, y * r, -PI, +PI, sd ); } -void drawArc( Vector3 c, Vector3 x, Vector3 y, float an, float ax, int sd ) +void drawArc( const Vector3 & c, const Vector3 & x, const Vector3 & y, float an, float ax, int sd ) { glBegin( GL_LINE_STRIP ); @@ -445,7 +445,7 @@ void drawArc( Vector3 c, Vector3 x, Vector3 y, float an, float ax, int sd ) glEnd(); } -void drawCone( Vector3 c, Vector3 n, float a, int sd ) +void drawCone( const Vector3 & c, Vector3 n, float a, int sd ) { Vector3 x = Vector3::crossproduct( n, Vector3( n[1], n[2], n[0] ) ); Vector3 y = Vector3::crossproduct( n, x ); @@ -479,7 +479,7 @@ void drawCone( Vector3 c, Vector3 n, float a, int sd ) glEnd(); } -void drawRagdollCone( Vector3 pivot, Vector3 twist, Vector3 plane, float coneAngle, float minPlaneAngle, float maxPlaneAngle, int sd ) +void drawRagdollCone( const Vector3 & pivot, const Vector3 & twist, const Vector3 & plane, float coneAngle, float minPlaneAngle, float maxPlaneAngle, int sd ) { Vector3 z = twist; Vector3 y = plane; @@ -516,7 +516,7 @@ void drawRagdollCone( Vector3 pivot, Vector3 twist, Vector3 plane, float coneAng glEnd(); } -void drawSpring( Vector3 a, Vector3 b, float stiffness, int sd, bool solid ) +void drawSpring( const Vector3 & a, const Vector3 & b, float stiffness, int sd, bool solid ) { // draw a spring with stiffness turns bool cull = glIsEnabled( GL_CULL_FACE ); @@ -605,7 +605,7 @@ void drawRail( const Vector3 & a, const Vector3 & b ) glEnd(); } -void drawSolidArc( Vector3 c, Vector3 n, Vector3 x, Vector3 y, float an, float ax, float r, int sd ) +void drawSolidArc( const Vector3 & c, const Vector3 & n, const Vector3 & x, const Vector3 & y, float an, float ax, float r, int sd ) { bool cull = glIsEnabled( GL_CULL_FACE ); glDisable( GL_CULL_FACE ); @@ -624,14 +624,14 @@ void drawSolidArc( Vector3 c, Vector3 n, Vector3 x, Vector3 y, float an, float a glEnable( GL_CULL_FACE ); } -void drawSphereSimple( Vector3 c, float r, int sd ) +void drawSphereSimple( const Vector3 & c, float r, int sd ) { drawCircle( c, Vector3( 0, 0, 1 ), r, sd ); drawCircle( c, Vector3( 0, 1, 0 ), r, sd ); drawCircle( c, Vector3( 1, 0, 0 ), r, sd ); } -void drawSphere( Vector3 c, float r, int sd ) +void drawSphere( const Vector3 & c, float r, int sd ) { for ( int j = -sd; j <= sd; j++ ) { float f = PI * float(j) / float(sd); @@ -673,7 +673,7 @@ void drawSphere( Vector3 c, float r, int sd ) } } -void drawCapsule( Vector3 a, Vector3 b, float r, int sd ) +void drawCapsule( const Vector3 & a, const Vector3 & b, float r, int sd ) { Vector3 d = b - a; @@ -727,7 +727,7 @@ void drawCapsule( Vector3 a, Vector3 b, float r, int sd ) } } -void drawDashLine( Vector3 a, Vector3 b, int sd ) +void drawDashLine( const Vector3 & a, const Vector3 & b, int sd ) { Vector3 d = ( b - a ) / float(sd); glBegin( GL_LINES ); diff --git a/src/gl/gltools.h b/src/gl/gltools.h index cfaa05980..32f4f29dc 100644 --- a/src/gl/gltools.h +++ b/src/gl/gltools.h @@ -113,23 +113,23 @@ class SkinPartition final QVector sortAxes( QVector axesDots ); -void drawAxes( Vector3 c, float axis ); -void drawAxesOverlay( Vector3 c, float axis, QVector axesOrder = {2, 1, 0} ); +void drawAxes( const Vector3 & c, float axis ); +void drawAxesOverlay( const Vector3 & c, float axis, QVector axesOrder = {2, 1, 0} ); void drawGrid( int s, int line, int sub ); -void drawBox( Vector3 a, Vector3 b ); -void drawCircle( Vector3 c, Vector3 n, float r, int sd = 16 ); -void drawArc( Vector3 c, Vector3 x, Vector3 y, float an, float ax, int sd = 8 ); -void drawSolidArc( Vector3 c, Vector3 n, Vector3 x, Vector3 y, float an, float ax, float r, int sd = 8 ); -void drawCone( Vector3 c, Vector3 n, float a, int sd = 16 ); -void drawRagdollCone( Vector3 pivot, Vector3 twist, Vector3 plane, float coneAngle, float minPlaneAngle, float maxPlaneAngle, int sd = 16 ); -void drawSphereSimple( Vector3 c, float r, int sd = 36 ); -void drawSphere( Vector3 c, float r, int sd = 8 ); -void drawCapsule( Vector3 a, Vector3 b, float r, int sd = 5 ); -void drawDashLine( Vector3 a, Vector3 b, int sd = 15 ); +void drawBox( const Vector3 & a, const Vector3 & b ); +void drawCircle( const Vector3 & c, const Vector3 & n, float r, int sd = 16 ); +void drawArc( const Vector3 & c, const Vector3 & x, const Vector3 & y, float an, float ax, int sd = 8 ); +void drawSolidArc( const Vector3 & c, const Vector3 & n, const Vector3 & x, const Vector3 & y, float an, float ax, float r, int sd = 8 ); +void drawCone( const Vector3 & c, Vector3 n, float a, int sd = 16 ); +void drawRagdollCone( const Vector3 & pivot, const Vector3 & twist, const Vector3 & plane, float coneAngle, float minPlaneAngle, float maxPlaneAngle, int sd = 16 ); +void drawSphereSimple( const Vector3 & c, float r, int sd = 36 ); +void drawSphere( const Vector3 & c, float r, int sd = 8 ); +void drawCapsule( const Vector3 & a, const Vector3 & b, float r, int sd = 5 ); +void drawDashLine( const Vector3 & a, const Vector3 & b, int sd = 15 ); void drawConvexHull( const NifModel * nif, const QModelIndex & iShape, float scale, bool solid = false ); void drawNiTSS( const NifModel * nif, const QModelIndex & iShape, bool solid = false ); void drawCMS( const NifModel * nif, const QModelIndex & iShape, bool solid = false ); -void drawSpring( Vector3 a, Vector3 b, float stiffness, int sd = 16, bool solid = false ); +void drawSpring( const Vector3 & a, const Vector3 & b, float stiffness, int sd = 16, bool solid = false ); void drawRail( const Vector3 & a, const Vector3 & b ); inline void glTranslate( const Vector3 & v ) diff --git a/src/glview.cpp b/src/glview.cpp index 7d4a84d3a..4ef5ba333 100644 --- a/src/glview.cpp +++ b/src/glview.cpp @@ -988,7 +988,7 @@ void GLView::setPosition( float x, float y, float z ) update(); } -void GLView::setPosition( Vector3 v ) +void GLView::setPosition( const Vector3 & v ) { Pos = v; update(); @@ -1567,8 +1567,9 @@ void GLView::saveImage() void GLView::dragEnterEvent( QDragEnterEvent * e ) { - if ( e->mimeData()->hasUrls() && e->mimeData()->urls().count() == 1 ) { - QUrl url = e->mimeData()->urls().first(); + auto md = e->mimeData(); + if ( md && md->hasUrls() && md->urls().count() == 1 ) { + QUrl url = md->urls().first(); if ( url.scheme() == "file" ) { QString fn = url.toLocalFile(); diff --git a/src/glview.h b/src/glview.h index cf9c621a3..4ea082bfb 100644 --- a/src/glview.h +++ b/src/glview.h @@ -144,7 +144,7 @@ class GLView final : public QGLWidget void setCenter(); void setDistance( float ); void setPosition( float, float, float ); - void setPosition( Vector3 ); + void setPosition( const Vector3 & ); void setProjection( bool ); void setRotation( float, float, float ); void setZoom( float ); diff --git a/src/importex/3ds.cpp b/src/importex/3ds.cpp index 61ff58f8e..4bbd207d1 100644 --- a/src/importex/3ds.cpp +++ b/src/importex/3ds.cpp @@ -98,7 +98,7 @@ struct objKfSequence } }; -static void addLink( NifModel * nif, QModelIndex iBlock, QString name, qint32 link ) +static void addLink( NifModel * nif, const QModelIndex & iBlock, const QString & name, qint32 link ) { QModelIndex iArray = nif->getIndex( iBlock, name ); QModelIndex iSize = nif->getIndex( iBlock, QString( "Num %1" ).arg( name ) ); diff --git a/src/importex/obj.cpp b/src/importex/obj.cpp index 136b39750..c7118716c 100644 --- a/src/importex/obj.cpp +++ b/src/importex/obj.cpp @@ -528,7 +528,7 @@ static void readMtlLib( const QString & fname, QMap & omat omaterials.insert( mtlid, mtl ); } -static void addLink( NifModel * nif, QModelIndex iBlock, QString name, qint32 link ) +static void addLink( NifModel * nif, const QModelIndex & iBlock, const QString & name, qint32 link ) { QModelIndex iArray = nif->getIndex( iBlock, name ); QModelIndex iSize = nif->getIndex( iBlock, QString( "Num %1" ).arg( name ) ); diff --git a/src/nifmodel.cpp b/src/nifmodel.cpp index b9a7811be..d8c40d34b 100644 --- a/src/nifmodel.cpp +++ b/src/nifmodel.cpp @@ -839,7 +839,7 @@ QString NifModel::getBlockName( const QModelIndex & idx ) const return block->name(); } - return QString( "" ); + return {}; } QString NifModel::getBlockType( const QModelIndex & idx ) const @@ -850,7 +850,7 @@ QString NifModel::getBlockType( const QModelIndex & idx ) const return block->type(); } - return QString( "" ); + return {}; } int NifModel::getBlockNumber( const QModelIndex & idx ) const @@ -1205,15 +1205,14 @@ QVariant NifModel::data( const QModelIndex & idx, int role ) const break; case ValueCol: { - QString type = item->type(); const NifValue & value = item->value(); if ( value.type() == NifValue::tString || value.type() == NifValue::tFilePath ) { return QString( this->string( index ) ).replace( "\n", " " ).replace( "\r", " " ); } - else if ( item->value().type() == NifValue::tStringOffset ) + else if ( value.type() == NifValue::tStringOffset ) { - int ofs = item->value().get(); + int ofs = value.get(); if ( ofs < 0 || ofs == 0x0000FFFF ) return QString( "" ); @@ -1231,9 +1230,9 @@ QVariant NifModel::data( const QModelIndex & idx, int role ) const return tr( "" ); } - else if ( item->value().type() == NifValue::tStringIndex ) + else if ( value.type() == NifValue::tStringIndex ) { - int idx = item->value().get(); + int idx = value.get(); if ( idx == -1 ) return QString(); @@ -1246,10 +1245,10 @@ QVariant NifModel::data( const QModelIndex & idx, int role ) const return QString( "%2 [%1]" ).arg( idx ).arg( string ); } - else if ( item->value().type() == NifValue::tBlockTypeIndex ) + else if ( value.type() == NifValue::tBlockTypeIndex ) { - int idx = item->value().get(); + int idx = value.get(); int offset = idx & 0x7FFF; NifItem * blocktypes = getItemX( item, "Block Types" ); NifItem * blocktyp = ( blocktypes ? blocktypes->child( offset ) : 0 ); @@ -1259,9 +1258,9 @@ QVariant NifModel::data( const QModelIndex & idx, int role ) const return QString( "%2 [%1]" ).arg( idx ).arg( blocktyp->value().get() ); } - else if ( item->value().isLink() ) + else if ( value.isLink() ) { - int lnk = item->value().toLink(); + int lnk = value.toLink(); if ( lnk >= 0 ) { QModelIndex block = getBlock( lnk ); @@ -1279,17 +1278,17 @@ QVariant NifModel::data( const QModelIndex & idx, int role ) const return tr( "None" ); } - else if ( item->value().isCount() ) + else if ( value.isCount() ) { - QString optId = NifValue::enumOptionName( item->type(), item->value().toCount() ); + QString optId = NifValue::enumOptionName( item->type(), value.toCount() ); if ( optId.isEmpty() ) - return item->value().toString(); + return value.toString(); return QString( "%1" ).arg( optId ); } - return item->value().toString().replace( "\n", " " ).replace( "\r", " " ); + return value.toString().replace( "\n", " " ).replace( "\r", " " ); } break; case ArgCol: @@ -1359,8 +1358,6 @@ QVariant NifModel::data( const QModelIndex & idx, int role ) const } case Qt::ToolTipRole: { - QString tip; - switch ( column ) { case NameCol: { @@ -1550,7 +1547,6 @@ bool NifModel::setData( const QModelIndex & index, const QVariant & value, int r break; case NifModel::ValueCol: { - QString type = item->type(); NifValue & val = item->value(); if ( val.type() == NifValue::tString || val.type() == NifValue::tFilePath ) { diff --git a/src/nifskope.cpp b/src/nifskope.cpp index 218859617..7f41f2d56 100644 --- a/src/nifskope.cpp +++ b/src/nifskope.cpp @@ -114,9 +114,9 @@ QString NifSkope::fileFilter( const QString & ext ) QString filter; for ( int i = 0; i < filetypes.size(); i++ ) { - if ( filetypes.at( i ).second == ext ) { - filter = QString( "%1 (*.%2)" ).arg( filetypes.at( i ).first ).arg( filetypes.at( i ).second ); - } + auto ft = filetypes.at(i); + if ( ft.second == ext ) + filter = QString( "%1 (*.%2)" ).arg( ft.first ).arg( ft.second ); } return filter; @@ -1424,7 +1424,7 @@ void NifSkope::migrateSettings() const // Migrate lambda // Using a QHash of registry keys (stored in version.h), migrates from one version to another. - auto migrate = []( QSettings & migrateFrom, QSettings & migrateTo, const QHash migration ) { + auto migrate = []( QSettings & migrateFrom, QSettings & migrateTo, const QHash & migration ) { QHash::const_iterator i; for ( i = migration.begin(); i != migration.end(); ++i ) { QVariant val = migrateFrom.value( i.key() ); diff --git a/src/nifskope_ui.cpp b/src/nifskope_ui.cpp index 7977c5482..bfc4a2112 100644 --- a/src/nifskope_ui.cpp +++ b/src/nifskope_ui.cpp @@ -909,7 +909,7 @@ void NifSkope::onLoadComplete( bool success, QString & fname ) clearCurrentFile(); // Reset - currentFile = ""; + currentFile.clear(); setWindowFilePath( "" ); progress->reset(); } diff --git a/src/niftypes.h b/src/niftypes.h index e56c70f1e..1e7d3acd5 100644 --- a/src/niftypes.h +++ b/src/niftypes.h @@ -282,7 +282,7 @@ class Vector3 return *this; } //! Add operator - Vector3 operator+( Vector3 v ) const + Vector3 operator+( const Vector3 & v ) const { Vector3 w( *this ); return w += v; @@ -297,7 +297,7 @@ class Vector3 } //! Minus operator - Vector3 operator-( Vector3 v ) const + Vector3 operator-( const Vector3 & v ) const { Vector3 w( *this ); return w -= v; @@ -485,7 +485,7 @@ class ByteVector3 : public Vector3 }; //! QDebug stream operator for Vector3 -inline QDebug & operator<<( QDebug dbg, Vector3 v ) +inline QDebug & operator<<( QDebug dbg, const Vector3 & v ) { dbg.nospace() << "(" << v[0] << ", " << v[1] << ", " << v[2] << ")"; return dbg.space(); @@ -562,13 +562,13 @@ class Vector4 return *this; } //! Add operator - Vector4 operator+( Vector4 v ) const + Vector4 operator+( const Vector4 & v ) const { Vector4 w( *this ); return w += v; } //! Minus operator - Vector4 operator-( Vector4 v ) const + Vector4 operator-( const Vector4 & v ) const { Vector4 w( *this ); return w -= v; @@ -705,7 +705,7 @@ class Vector4 }; //! QDebug stream operator for Vector2 -inline QDebug & operator<<( QDebug dbg, Vector4 v ) +inline QDebug & operator<<( QDebug dbg, const Vector4 & v ) { dbg.nospace() << "(" << v[0] << ", " << v[1] << ", " << v[2] << ", " << v[3] << ")"; return dbg.space(); @@ -1150,7 +1150,7 @@ class Triangle }; //! QDebug stream operator for Triangle -inline QDebug & operator<<( QDebug dbg, Triangle t ) +inline QDebug & operator<<( QDebug dbg, const Triangle & t ) { dbg.nospace() << "(" << t[0] << "," << t[1] << "," << t[2] << ")"; return dbg.space(); @@ -1465,13 +1465,13 @@ inline Color3::Color3( const Color4 & c4 ) rgb[2] = c4[2]; } -inline QDebug & operator<<( QDebug dbg, Color3 c ) +inline QDebug & operator<<( QDebug dbg, const Color3 & c ) { dbg.nospace() << "(" << c[0] << ", " << c[1] << ", " << c[2] << ")"; return dbg.space(); } -inline QDebug & operator<<( QDebug dbg, Color4 c ) +inline QDebug & operator<<( QDebug dbg, const Color4 & c ) { dbg.nospace() << "(" << c[0] << ", " << c[1] << ", " << c[2] << ", " << c[3] << ")"; return dbg.space(); diff --git a/src/spells/blocks.cpp b/src/spells/blocks.cpp index b6015dbed..5fb32a240 100644 --- a/src/spells/blocks.cpp +++ b/src/spells/blocks.cpp @@ -29,7 +29,7 @@ * @param array The name of the link array * @param link A reference to the block to insert into the link array */ -static bool addLink( NifModel * nif, QModelIndex iParent, QString array, int link ) +static bool addLink( NifModel * nif, const QModelIndex & iParent, const QString & array, int link ) { QModelIndex iSize = nif->getIndex( iParent, QString( "Num %1" ).arg( array ) ); QModelIndex iArray = nif->getIndex( iParent, array ); @@ -68,7 +68,7 @@ static bool addLink( NifModel * nif, QModelIndex iParent, QString array, int lin * @param array The name of the link array * @param link A reference to the block to remove from the link array */ -static void delLink( NifModel * nif, QModelIndex iParent, QString array, int link ) +static void delLink( NifModel * nif, const QModelIndex & iParent, QString array, int link ) { QModelIndex iSize = nif->getIndex( iParent, QString( "Num %1" ).arg( array ) ); QModelIndex iArray = nif->getIndex( iParent, array ); @@ -909,7 +909,7 @@ class spFlattenBranch final : public Spell return iNode; } - void doNode( NifModel * nif, const QModelIndex & iNode, const QModelIndex & iParent, Transform tp ) + void doNode( NifModel * nif, const QModelIndex & iNode, const QModelIndex & iParent, const Transform & tp ) { if ( !nif->inherits( iNode, "NiNode" ) ) return; diff --git a/src/spells/optimize.cpp b/src/spells/optimize.cpp index e53d4f2b9..a31e7a91e 100644 --- a/src/spells/optimize.cpp +++ b/src/spells/optimize.cpp @@ -337,7 +337,7 @@ class spCombiTris final : public Spell } //! Determine if two shapes are identical - bool matches( const NifModel * nif, QModelIndex iTriA, QModelIndex iTriB ) + bool matches( const NifModel * nif, const QModelIndex & iTriA, const QModelIndex & iTriB ) { if ( iTriA == iTriB || nif->itemName( iTriA ) != nif->itemName( iTriB ) @@ -404,7 +404,7 @@ class spCombiTris final : public Spell } //! Determines if two sets of shape data are identical - bool dataMatches( const NifModel * nif, QModelIndex iDataA, QModelIndex iDataB ) + bool dataMatches( const NifModel * nif, const QModelIndex & iDataA, const QModelIndex & iDataB ) { if ( iDataA == iDataB ) return true; @@ -423,7 +423,7 @@ class spCombiTris final : public Spell } //! Combines meshes a and b ( a += b ) - void combine( NifModel * nif, QModelIndex iTriA, QModelIndex iTriB ) + void combine( NifModel * nif, const QModelIndex & iTriA, const QModelIndex & iTriB ) { nif->set( iTriB, "Flags", nif->get( iTriB, "Flags" ) | 1 ); @@ -496,7 +496,7 @@ class spCombiTris final : public Spell REGISTER_SPELL( spCombiTris ) -void scan( QModelIndex idx, NifModel * nif, QMap & usedStrings, bool hasCED ) +void scan( const QModelIndex & idx, NifModel * nif, QMap & usedStrings, bool hasCED ) { for ( int i = 0; i < nif->rowCount( idx ); i++ ) { auto child = idx.child( i, 2 ); diff --git a/src/spells/sanitize.cpp b/src/spells/sanitize.cpp index 34aa42d93..a2040c4a6 100644 --- a/src/spells/sanitize.cpp +++ b/src/spells/sanitize.cpp @@ -34,7 +34,7 @@ class spReorderLinks final : public Spell * For spReorderLinks this will determine a sort of geometry before nodes * in the children links array. */ - static bool compareChildLinks( QPair a, QPair b ) + static bool compareChildLinks( const QPair & a, const QPair & b ) { return a.second != b.second ? a.second : a.first < b.first; } @@ -529,7 +529,6 @@ class spFillBlankControllerTypes final : public Spell QModelIndex cast( NifModel * nif, const QModelIndex & ) override final { - QVector stringsToAdd; QVector modifiedNames; auto iHeader = nif->getHeader(); diff --git a/src/spells/stringpalette.cpp b/src/spells/stringpalette.cpp index 2357f1222..ed37030c7 100644 --- a/src/spells/stringpalette.cpp +++ b/src/spells/stringpalette.cpp @@ -160,7 +160,7 @@ class spEditStringOffset final : public Spell } //! Gets a particular string from a string palette - static QString getString( const QMap strings, int ofs ) + static QString getString( const QMap & strings, int ofs ) { if ( ofs >= 0 ) { QMapIterator it( strings ); diff --git a/src/widgets/nifview.h b/src/widgets/nifview.h index 13b9d01f2..b1d295828 100644 --- a/src/widgets/nifview.h +++ b/src/widgets/nifview.h @@ -114,7 +114,7 @@ class NifValueClipboard public: NifValue getValue() { return value; } - void setValue( NifValue val ) { value = val; } + void setValue( const NifValue & val ) { value = val; } private: NifValue value = NifValue(); diff --git a/src/widgets/uvedit.cpp b/src/widgets/uvedit.cpp index 19aef8d17..19e1b1a8b 100644 --- a/src/widgets/uvedit.cpp +++ b/src/widgets/uvedit.cpp @@ -936,7 +936,7 @@ bool UVWidget::setNifData( NifModel * nifModel, const QModelIndex & nifIndex ) if ( iTexProp.isValid() ) { QString texture = nif->get( iTexProp, "Source Texture" ); - if ( texture != "" ) { + if ( !texture.isEmpty() ) { texfile = TexCache::find( texture, nif->getFolder() ); return true; } From 9e6e23b649a5014ec8ee1a1d9d01395acbf13e95 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Fri, 23 Jun 2017 09:44:30 -0400 Subject: [PATCH 036/152] [Reorg] Initial file move --- NifSkope.pro | 154 +++++++++++------------ src/{ => data}/nifitem.h | 0 src/{ => data}/niftypes.cpp | 0 src/{ => data}/niftypes.h | 0 src/{ => data}/nifvalue.cpp | 0 src/{ => data}/nifvalue.h | 0 src/{ => io}/material.cpp | 0 src/{ => io}/material.h | 0 src/{ => lib}/importex/3ds.cpp | 0 src/{ => lib}/importex/3ds.h | 0 src/{ => lib}/importex/col.cpp | 0 src/{ => lib}/importex/importex.cpp | 0 src/{ => lib}/importex/obj.cpp | 0 src/{ => lib}/nvtristripwrapper.cpp | 0 src/{ => lib}/nvtristripwrapper.h | 0 src/{ => lib}/qhull.cpp | 0 src/{ => lib}/qhull.h | 0 src/{ => model}/basemodel.cpp | 0 src/{ => model}/basemodel.h | 0 src/{ => model}/kfmmodel.cpp | 0 src/{ => model}/kfmmodel.h | 0 src/{ => model}/nifdelegate.cpp | 0 src/{ => model}/nifmodel.cpp | 0 src/{ => model}/nifmodel.h | 0 src/{ => model}/nifproxy.cpp | 0 src/{ => model}/nifproxy.h | 0 src/{ => ui}/widgets/colorwheel.cpp | 0 src/{ => ui}/widgets/colorwheel.h | 0 src/{ => ui}/widgets/fileselect.cpp | 0 src/{ => ui}/widgets/fileselect.h | 0 src/{ => ui}/widgets/floatedit.cpp | 0 src/{ => ui}/widgets/floatedit.h | 0 src/{ => ui}/widgets/floatslider.cpp | 0 src/{ => ui}/widgets/floatslider.h | 0 src/{ => ui}/widgets/groupbox.cpp | 0 src/{ => ui}/widgets/groupbox.h | 0 src/{ => ui}/widgets/inspect.cpp | 0 src/{ => ui}/widgets/inspect.h | 0 src/{ => ui}/widgets/nifcheckboxlist.cpp | 0 src/{ => ui}/widgets/nifcheckboxlist.h | 0 src/{ => ui}/widgets/nifeditors.cpp | 0 src/{ => ui}/widgets/nifeditors.h | 0 src/{ => ui}/widgets/nifview.cpp | 0 src/{ => ui}/widgets/nifview.h | 0 src/{ => ui}/widgets/refrbrowser.cpp | 0 src/{ => ui}/widgets/refrbrowser.h | 0 src/{ => ui}/widgets/uvedit.cpp | 0 src/{ => ui}/widgets/uvedit.h | 0 src/{ => ui}/widgets/valueedit.cpp | 0 src/{ => ui}/widgets/valueedit.h | 0 src/{ => ui}/widgets/xmlcheck.cpp | 0 src/{ => ui}/widgets/xmlcheck.h | 0 src/{ => xml}/kfmxml.cpp | 0 src/{ => xml}/nifexpr.cpp | 0 src/{ => xml}/nifexpr.h | 0 src/{ => xml}/nifxml.cpp | 0 56 files changed, 77 insertions(+), 77 deletions(-) rename src/{ => data}/nifitem.h (100%) rename src/{ => data}/niftypes.cpp (100%) rename src/{ => data}/niftypes.h (100%) rename src/{ => data}/nifvalue.cpp (100%) rename src/{ => data}/nifvalue.h (100%) rename src/{ => io}/material.cpp (100%) rename src/{ => io}/material.h (100%) rename src/{ => lib}/importex/3ds.cpp (100%) rename src/{ => lib}/importex/3ds.h (100%) rename src/{ => lib}/importex/col.cpp (100%) rename src/{ => lib}/importex/importex.cpp (100%) rename src/{ => lib}/importex/obj.cpp (100%) rename src/{ => lib}/nvtristripwrapper.cpp (100%) rename src/{ => lib}/nvtristripwrapper.h (100%) rename src/{ => lib}/qhull.cpp (100%) rename src/{ => lib}/qhull.h (100%) rename src/{ => model}/basemodel.cpp (100%) rename src/{ => model}/basemodel.h (100%) rename src/{ => model}/kfmmodel.cpp (100%) rename src/{ => model}/kfmmodel.h (100%) rename src/{ => model}/nifdelegate.cpp (100%) rename src/{ => model}/nifmodel.cpp (100%) rename src/{ => model}/nifmodel.h (100%) rename src/{ => model}/nifproxy.cpp (100%) rename src/{ => model}/nifproxy.h (100%) rename src/{ => ui}/widgets/colorwheel.cpp (100%) rename src/{ => ui}/widgets/colorwheel.h (100%) rename src/{ => ui}/widgets/fileselect.cpp (100%) rename src/{ => ui}/widgets/fileselect.h (100%) rename src/{ => ui}/widgets/floatedit.cpp (100%) rename src/{ => ui}/widgets/floatedit.h (100%) rename src/{ => ui}/widgets/floatslider.cpp (100%) rename src/{ => ui}/widgets/floatslider.h (100%) rename src/{ => ui}/widgets/groupbox.cpp (100%) rename src/{ => ui}/widgets/groupbox.h (100%) rename src/{ => ui}/widgets/inspect.cpp (100%) rename src/{ => ui}/widgets/inspect.h (100%) rename src/{ => ui}/widgets/nifcheckboxlist.cpp (100%) rename src/{ => ui}/widgets/nifcheckboxlist.h (100%) rename src/{ => ui}/widgets/nifeditors.cpp (100%) rename src/{ => ui}/widgets/nifeditors.h (100%) rename src/{ => ui}/widgets/nifview.cpp (100%) rename src/{ => ui}/widgets/nifview.h (100%) rename src/{ => ui}/widgets/refrbrowser.cpp (100%) rename src/{ => ui}/widgets/refrbrowser.h (100%) rename src/{ => ui}/widgets/uvedit.cpp (100%) rename src/{ => ui}/widgets/uvedit.h (100%) rename src/{ => ui}/widgets/valueedit.cpp (100%) rename src/{ => ui}/widgets/valueedit.h (100%) rename src/{ => ui}/widgets/xmlcheck.cpp (100%) rename src/{ => ui}/widgets/xmlcheck.h (100%) rename src/{ => xml}/kfmxml.cpp (100%) rename src/{ => xml}/nifexpr.cpp (100%) rename src/{ => xml}/nifexpr.h (100%) rename src/{ => xml}/nifxml.cpp (100%) diff --git a/NifSkope.pro b/NifSkope.pro index 2ffe254f3..e8a2c64af 100644 --- a/NifSkope.pro +++ b/NifSkope.pro @@ -34,8 +34,6 @@ CONFIG(debug, debug|release) { # uncomment this if you want the text stats gl option # DEFINES += USE_GL_QPAINTER -INCLUDEPATH += src lib - TRANSLATIONS += \ res/lang/NifSkope_de.ts \ res/lang/NifSkope_fr.ts @@ -137,9 +135,13 @@ include(NifSkope_targets.pri) ## PROJECT SCOPES ############################### +INCLUDEPATH += src lib src/lib src/data src/io src/model src/ui src/xml + HEADERS += \ - src/basemodel.h \ src/config.h \ + src/data/nifitem.h \ + src/data/niftypes.h \ + src/data/nifvalue.h \ src/gl/dds/BlockDXT.h \ src/gl/dds/Color.h \ src/gl/dds/ColorBlock.h \ @@ -149,6 +151,9 @@ HEADERS += \ src/gl/dds/Image.h \ src/gl/dds/PixelFormat.h \ src/gl/dds/Stream.h \ + src/gl/marker/constraints.h \ + src/gl/marker/furniture.h \ + src/gl/bsshape.h \ src/gl/controllers.h \ src/gl/glcontroller.h \ src/gl/glmarker.h \ @@ -161,24 +166,15 @@ HEADERS += \ src/gl/gltexloaders.h \ src/gl/gltools.h \ src/gl/icontrollable.h \ - src/gl/marker/constraints.h \ - src/gl/marker/furniture.h \ src/gl/renderer.h \ - src/glview.h \ - src/importex/3ds.h \ - src/kfmmodel.h \ - src/message.h \ - src/nifexpr.h \ - src/nifitem.h \ - src/nifmodel.h \ - src/nifproxy.h \ - src/nifskope.h \ - src/niftypes.h \ - src/nifvalue.h \ - src/nvtristripwrapper.h \ - src/qhull.h \ - src/settings.h \ - src/spellbook.h \ + src/io/material.h \ + src/lib/importex/3ds.h \ + src/lib/nvtristripwrapper.h \ + src/lib/qhull.h \ + src/model/basemodel.h \ + src/model/kfmmodel.h \ + src/model/nifmodel.h \ + src/model/nifproxy.h \ src/spells/blocks.h \ src/spells/mesh.h \ src/spells/misc.h \ @@ -187,37 +183,43 @@ HEADERS += \ src/spells/tangentspace.h \ src/spells/texture.h \ src/spells/transform.h \ - src/widgets/colorwheel.h \ - src/widgets/fileselect.h \ - src/widgets/floatedit.h \ - src/widgets/floatslider.h \ - src/widgets/groupbox.h \ - src/widgets/inspect.h \ - src/widgets/nifcheckboxlist.h \ - src/widgets/nifeditors.h \ - src/widgets/nifview.h \ - src/widgets/refrbrowser.h \ - src/widgets/uvedit.h \ - src/widgets/valueedit.h \ - src/widgets/xmlcheck.h \ + src/ui/widgets/colorwheel.h \ + src/ui/widgets/fileselect.h \ + src/ui/widgets/floatedit.h \ + src/ui/widgets/floatslider.h \ + src/ui/widgets/groupbox.h \ + src/ui/widgets/inspect.h \ + src/ui/widgets/nifcheckboxlist.h \ + src/ui/widgets/nifeditors.h \ + src/ui/widgets/nifview.h \ + src/ui/widgets/refrbrowser.h \ + src/ui/widgets/uvedit.h \ + src/ui/widgets/valueedit.h \ + src/ui/widgets/xmlcheck.h \ src/ui/about_dialog.h \ src/ui/checkablemessagebox.h \ src/ui/settingsdialog.h \ + src/xml/nifexpr.h \ + src/glview.h \ + src/message.h \ + src/nifskope.h \ + src/settings.h \ + src/spellbook.h \ src/version.h \ - lib/half.h \ lib/dds.h \ lib/dxgiformat.h \ - src/gl/bsshape.h \ - src/material.h + lib/half.h SOURCES += \ - src/basemodel.cpp \ + src/data/niftypes.cpp \ + src/data/nifvalue.cpp \ src/gl/dds/BlockDXT.cpp \ src/gl/dds/ColorBlock.cpp \ src/gl/dds/dds_api.cpp \ src/gl/dds/DirectDrawSurface.cpp \ src/gl/dds/Image.cpp \ src/gl/dds/Stream.cpp \ + src/gl/bsshape.cpp \ src/gl/controllers.cpp \ src/gl/glcontroller.cpp \ src/gl/glmarker.cpp \ @@ -230,27 +232,18 @@ SOURCES += \ src/gl/gltexloaders.cpp \ src/gl/gltools.cpp \ src/gl/renderer.cpp \ - src/glview.cpp \ - src/importex/3ds.cpp \ - src/importex/importex.cpp \ - src/importex/obj.cpp \ - src/importex/col.cpp \ - src/kfmmodel.cpp \ - src/kfmxml.cpp \ - src/message.cpp \ - src/nifdelegate.cpp \ - src/nifexpr.cpp \ - src/nifmodel.cpp \ - src/nifproxy.cpp \ - src/nifskope.cpp \ - src/nifskope_ui.cpp \ - src/niftypes.cpp \ - src/nifvalue.cpp \ - src/nifxml.cpp \ - src/nvtristripwrapper.cpp \ - src/qhull.cpp \ - src/settings.cpp \ - src/spellbook.cpp \ + src/io/material.cpp \ + src/lib/importex/3ds.cpp \ + src/lib/importex/importex.cpp \ + src/lib/importex/obj.cpp \ + src/lib/importex/col.cpp \ + src/lib/nvtristripwrapper.cpp \ + src/lib/qhull.cpp \ + src/model/basemodel.cpp \ + src/model/kfmmodel.cpp \ + src/model/nifdelegate.cpp \ + src/model/nifmodel.cpp \ + src/model/nifproxy.cpp \ src/spells/animation.cpp \ src/spells/blocks.cpp \ src/spells/bounds.cpp \ @@ -274,26 +267,33 @@ SOURCES += \ src/spells/tangentspace.cpp \ src/spells/texture.cpp \ src/spells/transform.cpp \ - src/widgets/colorwheel.cpp \ - src/widgets/fileselect.cpp \ - src/widgets/floatedit.cpp \ - src/widgets/floatslider.cpp \ - src/widgets/groupbox.cpp \ - src/widgets/inspect.cpp \ - src/widgets/nifcheckboxlist.cpp \ - src/widgets/nifeditors.cpp \ - src/widgets/nifview.cpp \ - src/widgets/refrbrowser.cpp \ - src/widgets/uvedit.cpp \ - src/widgets/valueedit.cpp \ - src/widgets/xmlcheck.cpp \ + src/ui/widgets/colorwheel.cpp \ + src/ui/widgets/fileselect.cpp \ + src/ui/widgets/floatedit.cpp \ + src/ui/widgets/floatslider.cpp \ + src/ui/widgets/groupbox.cpp \ + src/ui/widgets/inspect.cpp \ + src/ui/widgets/nifcheckboxlist.cpp \ + src/ui/widgets/nifeditors.cpp \ + src/ui/widgets/nifview.cpp \ + src/ui/widgets/refrbrowser.cpp \ + src/ui/widgets/uvedit.cpp \ + src/ui/widgets/valueedit.cpp \ + src/ui/widgets/xmlcheck.cpp \ src/ui/about_dialog.cpp \ src/ui/checkablemessagebox.cpp \ src/ui/settingsdialog.cpp \ + src/xml/kfmxml.cpp \ + src/xml/nifexpr.cpp \ + src/xml/nifxml.cpp \ + src/glview.cpp \ + src/message.cpp \ + src/nifskope.cpp \ + src/nifskope_ui.cpp \ + src/settings.cpp \ + src/spellbook.cpp \ src/version.cpp \ - lib/half.cpp \ - src/gl/bsshape.cpp \ - src/material.cpp + lib/half.cpp RESOURCES += \ res/nifskope.qrc @@ -303,9 +303,9 @@ FORMS += \ src/ui/checkablemessagebox.ui \ src/ui/nifskope.ui \ src/ui/settingsdialog.ui \ - src/ui/settingsgeneral.ui \ - src/ui/settingsrender.ui \ - src/ui/settingsresources.ui + src/ui/settingsgeneral.ui \ + src/ui/settingsrender.ui \ + src/ui/settingsresources.ui ############################### diff --git a/src/nifitem.h b/src/data/nifitem.h similarity index 100% rename from src/nifitem.h rename to src/data/nifitem.h diff --git a/src/niftypes.cpp b/src/data/niftypes.cpp similarity index 100% rename from src/niftypes.cpp rename to src/data/niftypes.cpp diff --git a/src/niftypes.h b/src/data/niftypes.h similarity index 100% rename from src/niftypes.h rename to src/data/niftypes.h diff --git a/src/nifvalue.cpp b/src/data/nifvalue.cpp similarity index 100% rename from src/nifvalue.cpp rename to src/data/nifvalue.cpp diff --git a/src/nifvalue.h b/src/data/nifvalue.h similarity index 100% rename from src/nifvalue.h rename to src/data/nifvalue.h diff --git a/src/material.cpp b/src/io/material.cpp similarity index 100% rename from src/material.cpp rename to src/io/material.cpp diff --git a/src/material.h b/src/io/material.h similarity index 100% rename from src/material.h rename to src/io/material.h diff --git a/src/importex/3ds.cpp b/src/lib/importex/3ds.cpp similarity index 100% rename from src/importex/3ds.cpp rename to src/lib/importex/3ds.cpp diff --git a/src/importex/3ds.h b/src/lib/importex/3ds.h similarity index 100% rename from src/importex/3ds.h rename to src/lib/importex/3ds.h diff --git a/src/importex/col.cpp b/src/lib/importex/col.cpp similarity index 100% rename from src/importex/col.cpp rename to src/lib/importex/col.cpp diff --git a/src/importex/importex.cpp b/src/lib/importex/importex.cpp similarity index 100% rename from src/importex/importex.cpp rename to src/lib/importex/importex.cpp diff --git a/src/importex/obj.cpp b/src/lib/importex/obj.cpp similarity index 100% rename from src/importex/obj.cpp rename to src/lib/importex/obj.cpp diff --git a/src/nvtristripwrapper.cpp b/src/lib/nvtristripwrapper.cpp similarity index 100% rename from src/nvtristripwrapper.cpp rename to src/lib/nvtristripwrapper.cpp diff --git a/src/nvtristripwrapper.h b/src/lib/nvtristripwrapper.h similarity index 100% rename from src/nvtristripwrapper.h rename to src/lib/nvtristripwrapper.h diff --git a/src/qhull.cpp b/src/lib/qhull.cpp similarity index 100% rename from src/qhull.cpp rename to src/lib/qhull.cpp diff --git a/src/qhull.h b/src/lib/qhull.h similarity index 100% rename from src/qhull.h rename to src/lib/qhull.h diff --git a/src/basemodel.cpp b/src/model/basemodel.cpp similarity index 100% rename from src/basemodel.cpp rename to src/model/basemodel.cpp diff --git a/src/basemodel.h b/src/model/basemodel.h similarity index 100% rename from src/basemodel.h rename to src/model/basemodel.h diff --git a/src/kfmmodel.cpp b/src/model/kfmmodel.cpp similarity index 100% rename from src/kfmmodel.cpp rename to src/model/kfmmodel.cpp diff --git a/src/kfmmodel.h b/src/model/kfmmodel.h similarity index 100% rename from src/kfmmodel.h rename to src/model/kfmmodel.h diff --git a/src/nifdelegate.cpp b/src/model/nifdelegate.cpp similarity index 100% rename from src/nifdelegate.cpp rename to src/model/nifdelegate.cpp diff --git a/src/nifmodel.cpp b/src/model/nifmodel.cpp similarity index 100% rename from src/nifmodel.cpp rename to src/model/nifmodel.cpp diff --git a/src/nifmodel.h b/src/model/nifmodel.h similarity index 100% rename from src/nifmodel.h rename to src/model/nifmodel.h diff --git a/src/nifproxy.cpp b/src/model/nifproxy.cpp similarity index 100% rename from src/nifproxy.cpp rename to src/model/nifproxy.cpp diff --git a/src/nifproxy.h b/src/model/nifproxy.h similarity index 100% rename from src/nifproxy.h rename to src/model/nifproxy.h diff --git a/src/widgets/colorwheel.cpp b/src/ui/widgets/colorwheel.cpp similarity index 100% rename from src/widgets/colorwheel.cpp rename to src/ui/widgets/colorwheel.cpp diff --git a/src/widgets/colorwheel.h b/src/ui/widgets/colorwheel.h similarity index 100% rename from src/widgets/colorwheel.h rename to src/ui/widgets/colorwheel.h diff --git a/src/widgets/fileselect.cpp b/src/ui/widgets/fileselect.cpp similarity index 100% rename from src/widgets/fileselect.cpp rename to src/ui/widgets/fileselect.cpp diff --git a/src/widgets/fileselect.h b/src/ui/widgets/fileselect.h similarity index 100% rename from src/widgets/fileselect.h rename to src/ui/widgets/fileselect.h diff --git a/src/widgets/floatedit.cpp b/src/ui/widgets/floatedit.cpp similarity index 100% rename from src/widgets/floatedit.cpp rename to src/ui/widgets/floatedit.cpp diff --git a/src/widgets/floatedit.h b/src/ui/widgets/floatedit.h similarity index 100% rename from src/widgets/floatedit.h rename to src/ui/widgets/floatedit.h diff --git a/src/widgets/floatslider.cpp b/src/ui/widgets/floatslider.cpp similarity index 100% rename from src/widgets/floatslider.cpp rename to src/ui/widgets/floatslider.cpp diff --git a/src/widgets/floatslider.h b/src/ui/widgets/floatslider.h similarity index 100% rename from src/widgets/floatslider.h rename to src/ui/widgets/floatslider.h diff --git a/src/widgets/groupbox.cpp b/src/ui/widgets/groupbox.cpp similarity index 100% rename from src/widgets/groupbox.cpp rename to src/ui/widgets/groupbox.cpp diff --git a/src/widgets/groupbox.h b/src/ui/widgets/groupbox.h similarity index 100% rename from src/widgets/groupbox.h rename to src/ui/widgets/groupbox.h diff --git a/src/widgets/inspect.cpp b/src/ui/widgets/inspect.cpp similarity index 100% rename from src/widgets/inspect.cpp rename to src/ui/widgets/inspect.cpp diff --git a/src/widgets/inspect.h b/src/ui/widgets/inspect.h similarity index 100% rename from src/widgets/inspect.h rename to src/ui/widgets/inspect.h diff --git a/src/widgets/nifcheckboxlist.cpp b/src/ui/widgets/nifcheckboxlist.cpp similarity index 100% rename from src/widgets/nifcheckboxlist.cpp rename to src/ui/widgets/nifcheckboxlist.cpp diff --git a/src/widgets/nifcheckboxlist.h b/src/ui/widgets/nifcheckboxlist.h similarity index 100% rename from src/widgets/nifcheckboxlist.h rename to src/ui/widgets/nifcheckboxlist.h diff --git a/src/widgets/nifeditors.cpp b/src/ui/widgets/nifeditors.cpp similarity index 100% rename from src/widgets/nifeditors.cpp rename to src/ui/widgets/nifeditors.cpp diff --git a/src/widgets/nifeditors.h b/src/ui/widgets/nifeditors.h similarity index 100% rename from src/widgets/nifeditors.h rename to src/ui/widgets/nifeditors.h diff --git a/src/widgets/nifview.cpp b/src/ui/widgets/nifview.cpp similarity index 100% rename from src/widgets/nifview.cpp rename to src/ui/widgets/nifview.cpp diff --git a/src/widgets/nifview.h b/src/ui/widgets/nifview.h similarity index 100% rename from src/widgets/nifview.h rename to src/ui/widgets/nifview.h diff --git a/src/widgets/refrbrowser.cpp b/src/ui/widgets/refrbrowser.cpp similarity index 100% rename from src/widgets/refrbrowser.cpp rename to src/ui/widgets/refrbrowser.cpp diff --git a/src/widgets/refrbrowser.h b/src/ui/widgets/refrbrowser.h similarity index 100% rename from src/widgets/refrbrowser.h rename to src/ui/widgets/refrbrowser.h diff --git a/src/widgets/uvedit.cpp b/src/ui/widgets/uvedit.cpp similarity index 100% rename from src/widgets/uvedit.cpp rename to src/ui/widgets/uvedit.cpp diff --git a/src/widgets/uvedit.h b/src/ui/widgets/uvedit.h similarity index 100% rename from src/widgets/uvedit.h rename to src/ui/widgets/uvedit.h diff --git a/src/widgets/valueedit.cpp b/src/ui/widgets/valueedit.cpp similarity index 100% rename from src/widgets/valueedit.cpp rename to src/ui/widgets/valueedit.cpp diff --git a/src/widgets/valueedit.h b/src/ui/widgets/valueedit.h similarity index 100% rename from src/widgets/valueedit.h rename to src/ui/widgets/valueedit.h diff --git a/src/widgets/xmlcheck.cpp b/src/ui/widgets/xmlcheck.cpp similarity index 100% rename from src/widgets/xmlcheck.cpp rename to src/ui/widgets/xmlcheck.cpp diff --git a/src/widgets/xmlcheck.h b/src/ui/widgets/xmlcheck.h similarity index 100% rename from src/widgets/xmlcheck.h rename to src/ui/widgets/xmlcheck.h diff --git a/src/kfmxml.cpp b/src/xml/kfmxml.cpp similarity index 100% rename from src/kfmxml.cpp rename to src/xml/kfmxml.cpp diff --git a/src/nifexpr.cpp b/src/xml/nifexpr.cpp similarity index 100% rename from src/nifexpr.cpp rename to src/xml/nifexpr.cpp diff --git a/src/nifexpr.h b/src/xml/nifexpr.h similarity index 100% rename from src/nifexpr.h rename to src/xml/nifexpr.h diff --git a/src/nifxml.cpp b/src/xml/nifxml.cpp similarity index 100% rename from src/nifxml.cpp rename to src/xml/nifxml.cpp From d26314a2bbddeda9e0eef231e875db6f832b749d Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Fri, 23 Jun 2017 10:02:39 -0400 Subject: [PATCH 037/152] [Reorg] Undo commands to own file --- NifSkope.pro | 2 + src/model/nifdelegate.cpp | 1 + src/model/nifmodel.cpp | 86 -------------------------- src/model/nifmodel.h | 29 --------- src/model/undocommands.cpp | 121 +++++++++++++++++++++++++++++++++++++ src/model/undocommands.h | 73 ++++++++++++++++++++++ src/ui/widgets/nifview.cpp | 1 + 7 files changed, 198 insertions(+), 115 deletions(-) create mode 100644 src/model/undocommands.cpp create mode 100644 src/model/undocommands.h diff --git a/NifSkope.pro b/NifSkope.pro index e8a2c64af..1f9643760 100644 --- a/NifSkope.pro +++ b/NifSkope.pro @@ -175,6 +175,7 @@ HEADERS += \ src/model/kfmmodel.h \ src/model/nifmodel.h \ src/model/nifproxy.h \ + src/model/undocommands.h \ src/spells/blocks.h \ src/spells/mesh.h \ src/spells/misc.h \ @@ -244,6 +245,7 @@ SOURCES += \ src/model/nifdelegate.cpp \ src/model/nifmodel.cpp \ src/model/nifproxy.cpp \ + src/model/undocommands.cpp \ src/spells/animation.cpp \ src/spells/blocks.cpp \ src/spells/bounds.cpp \ diff --git a/src/model/nifdelegate.cpp b/src/model/nifdelegate.cpp index d93b39b04..d1283cfbf 100644 --- a/src/model/nifdelegate.cpp +++ b/src/model/nifdelegate.cpp @@ -37,6 +37,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nifmodel.h" #include "nifproxy.h" #include "spellbook.h" +#include "undocommands.h" #include "widgets/valueedit.h" #include "widgets/nifcheckboxlist.h" diff --git a/src/model/nifmodel.cpp b/src/model/nifmodel.cpp index d8c40d34b..df91d79c6 100644 --- a/src/model/nifmodel.cpp +++ b/src/model/nifmodel.cpp @@ -3049,89 +3049,3 @@ QVariant NifModelEval::operator()( const QVariant & v ) const return v; } - - -/* - * ChangeValueCommand - */ - -ChangeValueCommand::ChangeValueCommand( const QModelIndex & index, - const QVariant & value, const QString & valueString, const QString & valueType, NifModel * model ) - : QUndoCommand(), nif( model ), idx( index ) -{ - oldValue = index.data( Qt::EditRole ); - newValue = value; - - auto oldTxt = index.data( Qt::DisplayRole ).toString(); - auto newTxt = valueString; - - if ( !newTxt.isEmpty() ) - setText( QApplication::translate( "ChangeValueCommand", "Set %1 to %2" ).arg( valueType ).arg( newTxt ) ); - else - setText( QApplication::translate( "ChangeValueCommand", "Modify %1" ).arg( valueType ) ); -} - -ChangeValueCommand::ChangeValueCommand( const QModelIndex & index, const NifValue & oldVal, - const NifValue & newVal, const QString & valueType, NifModel * model ) - : QUndoCommand(), nif( model ), idx( index ) -{ - oldValue = oldVal.toVariant(); - newValue = newVal.toVariant(); - - auto oldTxt = oldVal.toString(); - auto newTxt = newVal.toString(); - - if ( !newTxt.isEmpty() ) - setText( QApplication::translate( "ChangeValueCommand", "Set %1 to %2" ).arg( valueType ).arg( newTxt ) ); - else - setText( QApplication::translate( "ChangeValueCommand", "Modify %1" ).arg( valueType ) ); -} - -void ChangeValueCommand::redo() -{ - //qDebug() << "Redoing"; - nif->setData( idx, newValue, Qt::EditRole ); - - //qDebug() << nif->data( idx ).toString(); -} - -void ChangeValueCommand::undo() -{ - //qDebug() << "Undoing"; - nif->setData( idx, oldValue, Qt::EditRole ); - - //qDebug() << nif->data( idx ).toString(); -} - - -/* - * ToggleCheckBoxListCommand - */ - -ToggleCheckBoxListCommand::ToggleCheckBoxListCommand( const QModelIndex & index, - const QVariant & value, const QString & valueType, NifModel * model ) - : QUndoCommand(), nif( model ), idx( index ) -{ - oldValue = index.data( Qt::EditRole ); - newValue = value; - - auto oldTxt = index.data( Qt::DisplayRole ).toString(); - - setText( QApplication::translate( "ToggleCheckBoxListCommand", "Modify %1" ).arg( valueType ) ); -} - -void ToggleCheckBoxListCommand::redo() -{ - //qDebug() << "Redoing"; - nif->setData( idx, newValue, Qt::EditRole ); - - //qDebug() << nif->data( idx ).toString(); -} - -void ToggleCheckBoxListCommand::undo() -{ - //qDebug() << "Undoing"; - nif->setData( idx, oldValue, Qt::EditRole ); - - //qDebug() << nif->data( idx ).toString(); -} diff --git a/src/model/nifmodel.h b/src/model/nifmodel.h index 8590128b9..3a9ed9b80 100644 --- a/src/model/nifmodel.h +++ b/src/model/nifmodel.h @@ -392,35 +392,6 @@ class NifModelEval }; -class ChangeValueCommand : public QUndoCommand -{ -public: - ChangeValueCommand( const QModelIndex & index, const QVariant & value, - const QString & valueString, const QString & valueType, NifModel * model ); - ChangeValueCommand( const QModelIndex & index, const NifValue & oldValue, - const NifValue & newValue, const QString & valueType, NifModel * model ); - void redo() override; - void undo() override; -private: - NifModel * nif; - QVariant newValue, oldValue; - QModelIndex idx; -}; - - -class ToggleCheckBoxListCommand : public QUndoCommand -{ -public: - ToggleCheckBoxListCommand( const QModelIndex & index, const QVariant & value, const QString & valueType, NifModel * model ); - void redo() override; - void undo() override; -private: - NifModel * nif; - QVariant newValue, oldValue; - QModelIndex idx; -}; - - // Inlines diff --git a/src/model/undocommands.cpp b/src/model/undocommands.cpp new file mode 100644 index 000000000..bcf9d8e46 --- /dev/null +++ b/src/model/undocommands.cpp @@ -0,0 +1,121 @@ +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2017, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the NIF File Format Library and Tools project may not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +***** END LICENCE BLOCK *****/ + +#include "undocommands.h" + +#include "nifmodel.h" + + +/* + * ChangeValueCommand + */ + +ChangeValueCommand::ChangeValueCommand( const QModelIndex & index, + const QVariant & value, const QString & valueString, const QString & valueType, NifModel * model ) + : QUndoCommand(), nif( model ), idx( index ) +{ + oldValue = index.data( Qt::EditRole ); + newValue = value; + + auto oldTxt = index.data( Qt::DisplayRole ).toString(); + auto newTxt = valueString; + + if ( !newTxt.isEmpty() ) + setText( QApplication::translate( "ChangeValueCommand", "Set %1 to %2" ).arg( valueType ).arg( newTxt ) ); + else + setText( QApplication::translate( "ChangeValueCommand", "Modify %1" ).arg( valueType ) ); +} + +ChangeValueCommand::ChangeValueCommand( const QModelIndex & index, const NifValue & oldVal, + const NifValue & newVal, const QString & valueType, NifModel * model ) + : QUndoCommand(), nif( model ), idx( index ) +{ + oldValue = oldVal.toVariant(); + newValue = newVal.toVariant(); + + auto oldTxt = oldVal.toString(); + auto newTxt = newVal.toString(); + + if ( !newTxt.isEmpty() ) + setText( QApplication::translate( "ChangeValueCommand", "Set %1 to %2" ).arg( valueType ).arg( newTxt ) ); + else + setText( QApplication::translate( "ChangeValueCommand", "Modify %1" ).arg( valueType ) ); +} + +void ChangeValueCommand::redo() +{ + //qDebug() << "Redoing"; + nif->setData( idx, newValue, Qt::EditRole ); + + //qDebug() << nif->data( idx ).toString(); +} + +void ChangeValueCommand::undo() +{ + //qDebug() << "Undoing"; + nif->setData( idx, oldValue, Qt::EditRole ); + + //qDebug() << nif->data( idx ).toString(); +} + + +/* + * ToggleCheckBoxListCommand + */ + +ToggleCheckBoxListCommand::ToggleCheckBoxListCommand( const QModelIndex & index, + const QVariant & value, const QString & valueType, NifModel * model ) + : QUndoCommand(), nif( model ), idx( index ) +{ + oldValue = index.data( Qt::EditRole ); + newValue = value; + + auto oldTxt = index.data( Qt::DisplayRole ).toString(); + + setText( QApplication::translate( "ToggleCheckBoxListCommand", "Modify %1" ).arg( valueType ) ); +} + +void ToggleCheckBoxListCommand::redo() +{ + //qDebug() << "Redoing"; + nif->setData( idx, newValue, Qt::EditRole ); + + //qDebug() << nif->data( idx ).toString(); +} + +void ToggleCheckBoxListCommand::undo() +{ + //qDebug() << "Undoing"; + nif->setData( idx, oldValue, Qt::EditRole ); + + //qDebug() << nif->data( idx ).toString(); +} diff --git a/src/model/undocommands.h b/src/model/undocommands.h new file mode 100644 index 000000000..3b9b7b1b8 --- /dev/null +++ b/src/model/undocommands.h @@ -0,0 +1,73 @@ +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2017, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the NIF File Format Library and Tools project may not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +***** END LICENCE BLOCK *****/ + +#ifndef UNDOCOMMANDS_H +#define UNDOCOMMANDS_H + +#include +#include +#include + +#include "nifvalue.h" + +class NifModel; + +class ChangeValueCommand : public QUndoCommand +{ +public: + ChangeValueCommand( const QModelIndex & index, const QVariant & value, + const QString & valueString, const QString & valueType, NifModel * model ); + ChangeValueCommand( const QModelIndex & index, const NifValue & oldValue, + const NifValue & newValue, const QString & valueType, NifModel * model ); + void redo() override; + void undo() override; +private: + NifModel * nif; + QVariant newValue, oldValue; + QModelIndex idx; +}; + + +class ToggleCheckBoxListCommand : public QUndoCommand +{ +public: + ToggleCheckBoxListCommand( const QModelIndex & index, const QVariant & value, const QString & valueType, NifModel * model ); + void redo() override; + void undo() override; +private: + NifModel * nif; + QVariant newValue, oldValue; + QModelIndex idx; +}; + + +#endif // UNDOCOMMANDS_H diff --git a/src/ui/widgets/nifview.cpp b/src/ui/widgets/nifview.cpp index 9d90faf5c..a36a22b10 100644 --- a/src/ui/widgets/nifview.cpp +++ b/src/ui/widgets/nifview.cpp @@ -35,6 +35,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "basemodel.h" #include "nifproxy.h" #include "spellbook.h" +#include "undocommands.h" #include #include From 885b67cc94ae92a69b2d1109bb5bb3c7d65920ab Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Fri, 23 Jun 2017 10:07:10 -0400 Subject: [PATCH 038/152] [Reorg] Rename nifproxy .h/.cpp --- NifSkope.pro | 4 ++-- src/lib/importex/importex.cpp | 2 +- src/model/nifdelegate.cpp | 2 +- src/model/{nifproxy.cpp => nifproxymodel.cpp} | 2 +- src/model/{nifproxy.h => nifproxymodel.h} | 0 src/nifskope.cpp | 4 ++-- src/nifskope_ui.cpp | 2 +- src/ui/widgets/nifview.cpp | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) rename src/model/{nifproxy.cpp => nifproxymodel.cpp} (99%) rename src/model/{nifproxy.h => nifproxymodel.h} (100%) diff --git a/NifSkope.pro b/NifSkope.pro index 1f9643760..f8e1d26a5 100644 --- a/NifSkope.pro +++ b/NifSkope.pro @@ -174,7 +174,7 @@ HEADERS += \ src/model/basemodel.h \ src/model/kfmmodel.h \ src/model/nifmodel.h \ - src/model/nifproxy.h \ + src/model/nifproxymodel.h \ src/model/undocommands.h \ src/spells/blocks.h \ src/spells/mesh.h \ @@ -244,7 +244,7 @@ SOURCES += \ src/model/kfmmodel.cpp \ src/model/nifdelegate.cpp \ src/model/nifmodel.cpp \ - src/model/nifproxy.cpp \ + src/model/nifproxymodel.cpp \ src/model/undocommands.cpp \ src/spells/animation.cpp \ src/spells/blocks.cpp \ diff --git a/src/lib/importex/importex.cpp b/src/lib/importex/importex.cpp index 88a667d6f..d43bb3b94 100644 --- a/src/lib/importex/importex.cpp +++ b/src/lib/importex/importex.cpp @@ -31,7 +31,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "nifmodel.h" -#include "nifproxy.h" +#include "nifproxymodel.h" #include "nifskope.h" #include "widgets/nifview.h" diff --git a/src/model/nifdelegate.cpp b/src/model/nifdelegate.cpp index d1283cfbf..ff66deffc 100644 --- a/src/model/nifdelegate.cpp +++ b/src/model/nifdelegate.cpp @@ -35,7 +35,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "glview.h" #include "kfmmodel.h" #include "nifmodel.h" -#include "nifproxy.h" +#include "nifproxymodel.h" #include "spellbook.h" #include "undocommands.h" #include "widgets/valueedit.h" diff --git a/src/model/nifproxy.cpp b/src/model/nifproxymodel.cpp similarity index 99% rename from src/model/nifproxy.cpp rename to src/model/nifproxymodel.cpp index e0150b03c..655896660 100644 --- a/src/model/nifproxy.cpp +++ b/src/model/nifproxymodel.cpp @@ -30,7 +30,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ -#include "nifproxy.h" +#include "nifproxymodel.h" #include "nifmodel.h" diff --git a/src/model/nifproxy.h b/src/model/nifproxymodel.h similarity index 100% rename from src/model/nifproxy.h rename to src/model/nifproxymodel.h diff --git a/src/nifskope.cpp b/src/nifskope.cpp index 7f41f2d56..c714123e6 100644 --- a/src/nifskope.cpp +++ b/src/nifskope.cpp @@ -41,7 +41,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "gl/glscene.h" #include "kfmmodel.h" #include "nifmodel.h" -#include "nifproxy.h" +#include "nifproxymodel.h" #include "spellbook.h" #include "widgets/fileselect.h" #include "widgets/nifview.h" @@ -544,7 +544,7 @@ void NifSkope::setListMode() list->setRootIsDecorated( true ); QModelIndex pidx = proxy->mapFrom( idx, QModelIndex() ); list->setCurrentIndex( pidx ); - // proxy model has only two columns (see columnCount in nifproxy.h) + // proxy model has only two columns (see columnCount in nifproxymodel.h) list->setColumnHidden( 0, false ); list->setColumnHidden( 1, false ); head->resizeSection( 0, s0 ); diff --git a/src/nifskope_ui.cpp b/src/nifskope_ui.cpp index bfc4a2112..94915cd25 100644 --- a/src/nifskope_ui.cpp +++ b/src/nifskope_ui.cpp @@ -42,7 +42,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "gl/glscene.h" #include "kfmmodel.h" #include "nifmodel.h" -#include "nifproxy.h" +#include "nifproxymodel.h" #include "spellbook.h" #include "widgets/fileselect.h" #include "widgets/floatslider.h" diff --git a/src/ui/widgets/nifview.cpp b/src/ui/widgets/nifview.cpp index a36a22b10..985f82dc4 100644 --- a/src/ui/widgets/nifview.cpp +++ b/src/ui/widgets/nifview.cpp @@ -33,7 +33,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nifview.h" #include "basemodel.h" -#include "nifproxy.h" +#include "nifproxymodel.h" #include "spellbook.h" #include "undocommands.h" From b9a7cac7d1e9113903eedc80db3ed5a624fc2d64 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Fri, 23 Jun 2017 10:26:22 -0400 Subject: [PATCH 039/152] [Reorg] Move settings to ui/settingspane --- NifSkope.pro | 4 ++-- src/gl/bsshape.cpp | 1 - src/gl/controllers.cpp | 1 - src/gl/glcontroller.cpp | 1 - src/gl/glmesh.cpp | 1 - src/gl/glnode.cpp | 4 +++- src/gl/glscene.cpp | 1 - src/gl/gltex.cpp | 1 - src/gl/renderer.cpp | 4 +++- src/glview.cpp | 2 +- src/model/basemodel.cpp | 1 - src/model/basemodel.h | 2 ++ src/model/nifdelegate.cpp | 2 -- src/model/nifmodel.cpp | 1 - src/nifskope.cpp | 2 +- src/nifskope_ui.cpp | 1 - src/spells/animation.cpp | 1 - src/ui/settingsdialog.cpp | 2 +- src/{settings.cpp => ui/settingspane.cpp} | 2 +- src/{settings.h => ui/settingspane.h} | 0 src/ui/widgets/nifcheckboxlist.cpp | 1 - src/ui/widgets/uvedit.cpp | 4 +++- 22 files changed, 17 insertions(+), 22 deletions(-) rename src/{settings.cpp => ui/settingspane.cpp} (99%) rename src/{settings.h => ui/settingspane.h} (100%) diff --git a/NifSkope.pro b/NifSkope.pro index f8e1d26a5..e4f86968a 100644 --- a/NifSkope.pro +++ b/NifSkope.pro @@ -200,11 +200,11 @@ HEADERS += \ src/ui/about_dialog.h \ src/ui/checkablemessagebox.h \ src/ui/settingsdialog.h \ + src/ui/settingspane.h \ src/xml/nifexpr.h \ src/glview.h \ src/message.h \ src/nifskope.h \ - src/settings.h \ src/spellbook.h \ src/version.h \ lib/dds.h \ @@ -285,6 +285,7 @@ SOURCES += \ src/ui/about_dialog.cpp \ src/ui/checkablemessagebox.cpp \ src/ui/settingsdialog.cpp \ + src/ui/settingspane.cpp \ src/xml/kfmxml.cpp \ src/xml/nifexpr.cpp \ src/xml/nifxml.cpp \ @@ -292,7 +293,6 @@ SOURCES += \ src/message.cpp \ src/nifskope.cpp \ src/nifskope_ui.cpp \ - src/settings.cpp \ src/spellbook.cpp \ src/version.cpp \ lib/half.cpp diff --git a/src/gl/bsshape.cpp b/src/gl/bsshape.cpp index 1abc942e0..db0cac98a 100644 --- a/src/gl/bsshape.cpp +++ b/src/gl/bsshape.cpp @@ -1,5 +1,4 @@ #include "bsshape.h" -#include "settings.h" #include "glscene.h" #include "material.h" diff --git a/src/gl/controllers.cpp b/src/gl/controllers.cpp index a43f908a9..2a89528c6 100644 --- a/src/gl/controllers.cpp +++ b/src/gl/controllers.cpp @@ -31,7 +31,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "controllers.h" -#include "settings.h" #include "glmesh.h" #include "glnode.h" diff --git a/src/gl/glcontroller.cpp b/src/gl/glcontroller.cpp index 74aa9a33d..55215b36b 100644 --- a/src/gl/glcontroller.cpp +++ b/src/gl/glcontroller.cpp @@ -31,7 +31,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "glcontroller.h" -#include "settings.h" #include "glscene.h" diff --git a/src/gl/glmesh.cpp b/src/gl/glmesh.cpp index 28485a881..9810f7014 100644 --- a/src/gl/glmesh.cpp +++ b/src/gl/glmesh.cpp @@ -32,7 +32,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "glmesh.h" #include "config.h" -#include "settings.h" #include "controllers.h" #include "glscene.h" diff --git a/src/gl/glnode.cpp b/src/gl/glnode.cpp index 8fbfe6c95..a71caa121 100644 --- a/src/gl/glnode.cpp +++ b/src/gl/glnode.cpp @@ -31,7 +31,9 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "glnode.h" -#include "settings.h" + +#include "nifskope.h" +#include "ui/settingsdialog.h" #include "controllers.h" #include "glmarker.h" diff --git a/src/gl/glscene.cpp b/src/gl/glscene.cpp index f9ea31044..315eb8b03 100644 --- a/src/gl/glscene.cpp +++ b/src/gl/glscene.cpp @@ -31,7 +31,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "glscene.h" -#include "settings.h" #include "glcontroller.h" #include "glmesh.h" diff --git a/src/gl/gltex.cpp b/src/gl/gltex.cpp index afc33027d..34db20837 100644 --- a/src/gl/gltex.cpp +++ b/src/gl/gltex.cpp @@ -31,7 +31,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "gltex.h" -#include "settings.h" #include "glscene.h" #include "gltexloaders.h" diff --git a/src/gl/renderer.cpp b/src/gl/renderer.cpp index 843a54654..4b65af9ad 100644 --- a/src/gl/renderer.cpp +++ b/src/gl/renderer.cpp @@ -31,7 +31,9 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "renderer.h" -#include "settings.h" + +#include "nifskope.h" +#include "ui/settingsdialog.h" #include "glmesh.h" #include "glproperty.h" diff --git a/src/glview.cpp b/src/glview.cpp index 4ef5ba333..4adbd91de 100644 --- a/src/glview.cpp +++ b/src/glview.cpp @@ -32,7 +32,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "glview.h" #include "config.h" -#include "settings.h" +#include "ui/settingsdialog.h" #include "ui_nifskope.h" #include "nifskope.h" diff --git a/src/model/basemodel.cpp b/src/model/basemodel.cpp index 8965e62d0..9afeb9140 100644 --- a/src/model/basemodel.cpp +++ b/src/model/basemodel.cpp @@ -31,7 +31,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "basemodel.h" -#include "settings.h" #include "niftypes.h" diff --git a/src/model/basemodel.h b/src/model/basemodel.h index b051a0f93..a4a71e30a 100644 --- a/src/model/basemodel.h +++ b/src/model/basemodel.h @@ -45,6 +45,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#define NifSkopeDisplayRole (Qt::UserRole + 42) + //! @file basemodel.h BaseModel, BaseModelEval diff --git a/src/model/nifdelegate.cpp b/src/model/nifdelegate.cpp index ff66deffc..01c3004a0 100644 --- a/src/model/nifdelegate.cpp +++ b/src/model/nifdelegate.cpp @@ -30,8 +30,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ -#include "settings.h" - #include "glview.h" #include "kfmmodel.h" #include "nifmodel.h" diff --git a/src/model/nifmodel.cpp b/src/model/nifmodel.cpp index df91d79c6..002811c4d 100644 --- a/src/model/nifmodel.cpp +++ b/src/model/nifmodel.cpp @@ -32,7 +32,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nifmodel.h" #include "config.h" -#include "settings.h" #include "niftypes.h" #include "spellbook.h" diff --git a/src/nifskope.cpp b/src/nifskope.cpp index c714123e6..db258645a 100644 --- a/src/nifskope.cpp +++ b/src/nifskope.cpp @@ -32,10 +32,10 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nifskope.h" #include "version.h" -#include "settings.h" #include "ui_nifskope.h" #include "ui/about_dialog.h" +#include "ui/settingsdialog.h" #include "glview.h" #include "gl/glscene.h" diff --git a/src/nifskope_ui.cpp b/src/nifskope_ui.cpp index 94915cd25..201a11dfa 100644 --- a/src/nifskope_ui.cpp +++ b/src/nifskope_ui.cpp @@ -32,7 +32,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nifskope.h" #include "version.h" -#include "settings.h" #include "ui_nifskope.h" #include "ui/about_dialog.h" diff --git a/src/spells/animation.cpp b/src/spells/animation.cpp index 5a8e054ff..9d25a9aea 100644 --- a/src/spells/animation.cpp +++ b/src/spells/animation.cpp @@ -1,5 +1,4 @@ #include "spellbook.h" -#include "settings.h" #include diff --git a/src/ui/settingsdialog.cpp b/src/ui/settingsdialog.cpp index 177ee5345..a4fabf09b 100644 --- a/src/ui/settingsdialog.cpp +++ b/src/ui/settingsdialog.cpp @@ -1,7 +1,7 @@ #include "settingsdialog.h" #include "ui_settingsdialog.h" -#include "settings.h" +#include "settingspane.h" #include #include diff --git a/src/settings.cpp b/src/ui/settingspane.cpp similarity index 99% rename from src/settings.cpp rename to src/ui/settingspane.cpp index 31dca46f3..b155f3e5c 100644 --- a/src/settings.cpp +++ b/src/ui/settingspane.cpp @@ -1,4 +1,4 @@ -#include "settings.h" +#include "settingspane.h" #include "widgets/colorwheel.h" #include "widgets/floatslider.h" diff --git a/src/settings.h b/src/ui/settingspane.h similarity index 100% rename from src/settings.h rename to src/ui/settingspane.h diff --git a/src/ui/widgets/nifcheckboxlist.cpp b/src/ui/widgets/nifcheckboxlist.cpp index 467d61caa..2f6b59eb4 100644 --- a/src/ui/widgets/nifcheckboxlist.cpp +++ b/src/ui/widgets/nifcheckboxlist.cpp @@ -31,7 +31,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "nifcheckboxlist.h" -#include "settings.h" #include #include diff --git a/src/ui/widgets/uvedit.cpp b/src/ui/widgets/uvedit.cpp index 19e1b1a8b..d354dc44b 100644 --- a/src/ui/widgets/uvedit.cpp +++ b/src/ui/widgets/uvedit.cpp @@ -31,13 +31,14 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "uvedit.h" -#include "settings.h" #include "nifmodel.h" +#include "nifskope.h" #include "niftypes.h" #include "nvtristripwrapper.h" #include "gl/gltex.h" #include "gl/gltools.h" +#include "ui/settingsdialog.h" #include // QUndoCommand Inherited #include @@ -51,6 +52,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include // TODO: Determine the necessity of this // Appears to be used solely for gluErrorString From 0c1d2ec2ec8cd426e9a9e67e16968761c4396cb6 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Fri, 23 Jun 2017 10:30:26 -0400 Subject: [PATCH 040/152] [Reorg] Remove unused config.h --- NifSkope.pro | 1 - src/config.h | 50 ----------------------------------- src/data/nifvalue.cpp | 1 - src/gl/glmesh.cpp | 1 - src/glview.cpp | 1 - src/lib/importex/col.cpp | 2 -- src/model/nifmodel.cpp | 1 - src/spells/blocks.cpp | 1 - src/spells/texture.cpp | 1 - src/spells/transform.cpp | 1 - src/ui/widgets/fileselect.cpp | 1 - src/ui/widgets/xmlcheck.cpp | 1 - 12 files changed, 62 deletions(-) delete mode 100644 src/config.h diff --git a/NifSkope.pro b/NifSkope.pro index e4f86968a..2613fa6fc 100644 --- a/NifSkope.pro +++ b/NifSkope.pro @@ -138,7 +138,6 @@ include(NifSkope_targets.pri) INCLUDEPATH += src lib src/lib src/data src/io src/model src/ui src/xml HEADERS += \ - src/config.h \ src/data/nifitem.h \ src/data/niftypes.h \ src/data/nifvalue.h \ diff --git a/src/config.h b/src/config.h deleted file mode 100644 index 210f6c728..000000000 --- a/src/config.h +++ /dev/null @@ -1,50 +0,0 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2015, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -3. The name of the NIF File Format Library and Tools project may not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -***** END LICENCE BLOCK *****/ - -#ifndef CONFIG_H -#define CONFIG_H - -//#include -//#include "version.h" - - -/*! \file config.h - * \brief Configuration info - * - * Include this if you want to access the current version or persistent QSettings. - */ - -//! Create or use a QSettings variable for NifSkope -//#define NIFSKOPE_QSETTINGS( config ) QSettings config - - -#endif diff --git a/src/data/nifvalue.cpp b/src/data/nifvalue.cpp index f52c5898f..b8dc3d12c 100644 --- a/src/data/nifvalue.cpp +++ b/src/data/nifvalue.cpp @@ -31,7 +31,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "nifvalue.h" -#include "config.h" #include "half.h" #include "nifmodel.h" diff --git a/src/gl/glmesh.cpp b/src/gl/glmesh.cpp index 9810f7014..39c8b2ad7 100644 --- a/src/gl/glmesh.cpp +++ b/src/gl/glmesh.cpp @@ -31,7 +31,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "glmesh.h" -#include "config.h" #include "controllers.h" #include "glscene.h" diff --git a/src/glview.cpp b/src/glview.cpp index 4adbd91de..6a403a70d 100644 --- a/src/glview.cpp +++ b/src/glview.cpp @@ -31,7 +31,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "glview.h" -#include "config.h" #include "ui/settingsdialog.h" #include "ui_nifskope.h" diff --git a/src/lib/importex/col.cpp b/src/lib/importex/col.cpp index 6447c09e4..8a312c764 100644 --- a/src/lib/importex/col.cpp +++ b/src/lib/importex/col.cpp @@ -30,8 +30,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ -#include "config.h" - #include "nifmodel.h" #include "nvtristripwrapper.h" #include "gl/gltex.h" diff --git a/src/model/nifmodel.cpp b/src/model/nifmodel.cpp index 002811c4d..d0b13cb00 100644 --- a/src/model/nifmodel.cpp +++ b/src/model/nifmodel.cpp @@ -31,7 +31,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "nifmodel.h" -#include "config.h" #include "niftypes.h" #include "spellbook.h" diff --git a/src/spells/blocks.cpp b/src/spells/blocks.cpp index 5fb32a240..a13297ba5 100644 --- a/src/spells/blocks.cpp +++ b/src/spells/blocks.cpp @@ -1,5 +1,4 @@ #include "blocks.h" -#include "config.h" #include #include diff --git a/src/spells/texture.cpp b/src/spells/texture.cpp index 24f19c19e..e841e7f98 100644 --- a/src/spells/texture.cpp +++ b/src/spells/texture.cpp @@ -1,5 +1,4 @@ #include "texture.h" -#include "config.h" #include "blocks.h" #include "nvtristripwrapper.h" diff --git a/src/spells/transform.cpp b/src/spells/transform.cpp index 3ca710035..bf089a383 100644 --- a/src/spells/transform.cpp +++ b/src/spells/transform.cpp @@ -1,5 +1,4 @@ #include "transform.h" -#include "config.h" #include "widgets/nifeditors.h" diff --git a/src/ui/widgets/fileselect.cpp b/src/ui/widgets/fileselect.cpp index 48d26aa71..0c122bbc9 100644 --- a/src/ui/widgets/fileselect.cpp +++ b/src/ui/widgets/fileselect.cpp @@ -31,7 +31,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "fileselect.h" -#include "config.h" #include #include diff --git a/src/ui/widgets/xmlcheck.cpp b/src/ui/widgets/xmlcheck.cpp index 484d6f1d5..86bd81f1a 100644 --- a/src/ui/widgets/xmlcheck.cpp +++ b/src/ui/widgets/xmlcheck.cpp @@ -1,5 +1,4 @@ #include "xmlcheck.h" -#include "config.h" #include "kfmmodel.h" #include "nifmodel.h" From 42437a2ad6d10ca23104c3233ea93a348dae78ae Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Fri, 23 Jun 2017 11:08:56 -0400 Subject: [PATCH 041/152] [Reorg] NIF stream classes to own file --- NifSkope.pro | 2 + src/data/nifvalue.cpp | 883 +-------------------------------------- src/data/nifvalue.h | 100 ----- src/io/nifstream.cpp | 917 +++++++++++++++++++++++++++++++++++++++++ src/io/nifstream.h | 142 +++++++ src/model/kfmmodel.cpp | 1 + src/model/nifmodel.cpp | 1 + 7 files changed, 1064 insertions(+), 982 deletions(-) create mode 100644 src/io/nifstream.cpp create mode 100644 src/io/nifstream.h diff --git a/NifSkope.pro b/NifSkope.pro index 2613fa6fc..46dd65a99 100644 --- a/NifSkope.pro +++ b/NifSkope.pro @@ -167,6 +167,7 @@ HEADERS += \ src/gl/icontrollable.h \ src/gl/renderer.h \ src/io/material.h \ + src/io/nifstream.h \ src/lib/importex/3ds.h \ src/lib/nvtristripwrapper.h \ src/lib/qhull.h \ @@ -233,6 +234,7 @@ SOURCES += \ src/gl/gltools.cpp \ src/gl/renderer.cpp \ src/io/material.cpp \ + src/io/nifstream.cpp \ src/lib/importex/3ds.cpp \ src/lib/importex/importex.cpp \ src/lib/importex/obj.cpp \ diff --git a/src/data/nifvalue.cpp b/src/data/nifvalue.cpp index b8dc3d12c..f8329f39f 100644 --- a/src/data/nifvalue.cpp +++ b/src/data/nifvalue.cpp @@ -31,13 +31,9 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "nifvalue.h" - -#include "half.h" #include "nifmodel.h" -#include -#include -#include +#include //! @file nifvalue.cpp NifValue, NifIStream, NifOStream, NifSStream @@ -1012,881 +1008,4 @@ QColor NifValue::toColor() const return QColor(); } -/* - * NifIStream - */ - -void NifIStream::init() -{ - bool32bit = (model->inherits( "NifModel" ) && model->getVersionNumber() <= 0x04000002); - linkAdjust = (model->inherits( "NifModel" ) && model->getVersionNumber() < 0x0303000D); - stringAdjust = (model->inherits( "NifModel" ) && model->getVersionNumber() >= 0x14010003); - bigEndian = false; // set when tFileVersion is read - - dataStream = std::unique_ptr( new QDataStream( device ) ); - dataStream->setByteOrder( QDataStream::LittleEndian ); - dataStream->setFloatingPointPrecision( QDataStream::SinglePrecision ); - - maxLength = 0x8000; -} - -bool NifIStream::read( NifValue & val ) -{ - switch ( val.type() ) { - case NifValue::tBool: - { - val.val.u32 = 0; - - if ( bool32bit ) - *dataStream >> val.val.u32; - else - *dataStream >> val.val.u08; - - return ( dataStream->status() == QDataStream::Ok ); - } - case NifValue::tByte: - { - val.val.u32 = 0; - *dataStream >> val.val.u08; - return ( dataStream->status() == QDataStream::Ok ); - } - case NifValue::tWord: - case NifValue::tShort: - case NifValue::tFlags: - case NifValue::tBlockTypeIndex: - { - val.val.u32 = 0; - *dataStream >> val.val.u16; - return ( dataStream->status() == QDataStream::Ok ); - } - case NifValue::tStringOffset: - case NifValue::tInt: - case NifValue::tUInt: - { - *dataStream >> val.val.u32; - return ( dataStream->status() == QDataStream::Ok ); - } - case NifValue::tULittle32: - { - if ( bigEndian ) - dataStream->setByteOrder( QDataStream::LittleEndian ); - - *dataStream >> val.val.u32; - - if ( bigEndian ) - dataStream->setByteOrder( QDataStream::BigEndian ); - - return (dataStream->status() == QDataStream::Ok); - } - case NifValue::tStringIndex: - { - *dataStream >> val.val.u32; - return ( dataStream->status() == QDataStream::Ok ); - } - case NifValue::tLink: - case NifValue::tUpLink: - { - *dataStream >> val.val.i32; - - if ( linkAdjust ) - val.val.i32--; - - return ( dataStream->status() == QDataStream::Ok ); - } - case NifValue::tFloat: - { - *dataStream >> val.val.f32; - return ( dataStream->status() == QDataStream::Ok ); - } - case NifValue::tHfloat: - { - uint16_t half; - *dataStream >> half; - val.val.u32 = half_to_float( half ); - return (dataStream->status() == QDataStream::Ok); - } - case NifValue::tByteVector3: - { - quint8 x, y, z; - float xf, yf, zf; - - *dataStream >> x; - *dataStream >> y; - *dataStream >> z; - - xf = (double(x) / 255.0) * 2.0 - 1.0; - yf = (double(y) / 255.0) * 2.0 - 1.0; - zf = (double(z) / 255.0) * 2.0 - 1.0; - - Vector3 * v = static_cast(val.val.data); - v->xyz[0] = xf; v->xyz[1] = yf; v->xyz[2] = zf; - - return (dataStream->status() == QDataStream::Ok); - } - case NifValue::tHalfVector3: - { - uint16_t x, y, z; - union { float f; uint32_t i; } xu, yu, zu; - - *dataStream >> x; - *dataStream >> y; - *dataStream >> z; - - xu.i = half_to_float( x ); - yu.i = half_to_float( y ); - zu.i = half_to_float( z ); - - Vector3 * v = static_cast(val.val.data); - v->xyz[0] = xu.f; v->xyz[1] = yu.f; v->xyz[2] = zu.f; - - return ( dataStream->status() == QDataStream::Ok ); - } - case NifValue::tHalfVector2: - { - uint16_t x, y; - union { float f; uint32_t i; } xu, yu; - - *dataStream >> x; - *dataStream >> y; - - xu.i = half_to_float( x ); - yu.i = half_to_float( y ); - - Vector2 * v = static_cast(val.val.data); - v->xy[0] = xu.f; v->xy[1] = yu.f; - - return ( dataStream->status() == QDataStream::Ok ); - } - case NifValue::tVector3: - { - Vector3 * v = static_cast( val.val.data ); - *dataStream >> *v; - return ( dataStream->status() == QDataStream::Ok ); - } - case NifValue::tVector4: - { - Vector4 * v = static_cast( val.val.data ); - *dataStream >> *v; - return ( dataStream->status() == QDataStream::Ok ); - } - case NifValue::tTriangle: - { - Triangle * t = static_cast( val.val.data ); - *dataStream >> *t; - return ( dataStream->status() == QDataStream::Ok ); - } - case NifValue::tQuat: - { - Quat * q = static_cast( val.val.data ); - *dataStream >> *q; - return ( dataStream->status() == QDataStream::Ok ); - } - case NifValue::tQuatXYZW: - { - Quat * q = static_cast( val.val.data ); - return device->read( (char *)&q->wxyz[1], 12 ) == 12 && device->read( (char *)q->wxyz, 4 ) == 4; - } - case NifValue::tMatrix: - return device->read( (char *)static_cast( val.val.data )->m, 36 ) == 36; - case NifValue::tMatrix4: - return device->read( (char *)static_cast( val.val.data )->m, 64 ) == 64; - case NifValue::tVector2: - { - Vector2 * v = static_cast( val.val.data ); - *dataStream >> *v; - return ( dataStream->status() == QDataStream::Ok ); - } - case NifValue::tColor3: - return device->read( (char *)static_cast( val.val.data )->rgb, 12 ) == 12; - case NifValue::tByteColor4: - { - quint8 r, g, b, a; - *dataStream >> r; - *dataStream >> g; - *dataStream >> b; - *dataStream >> a; - - Color4 * c = static_cast(val.val.data); - c->setRGBA( (float)r / 255.0, (float)g / 255.0, (float)b / 255.0, (float)a / 255.0 ); - - return (dataStream->status() == QDataStream::Ok); - } - case NifValue::tColor4: - { - Color4 * c = static_cast( val.val.data ); - *dataStream >> *c; - return ( dataStream->status() == QDataStream::Ok ); - } - case NifValue::tSizedString: - { - int len; - //device->read( (char *) &len, 4 ); - *dataStream >> len; - - if ( len > maxLength || len < 0 ) { - *static_cast( val.val.data ) = tr( "" ).arg( len, 0, 16 ); return false; - } - - QByteArray string = device->read( len ); - - if ( string.size() != len ) - return false; - - //string.replace( "\r", "\\r" ); - //string.replace( "\n", "\\n" ); - *static_cast( val.val.data ) = QString( string ); - } - return true; - case NifValue::tShortString: - { - unsigned char len; - device->read( (char *)&len, 1 ); - QByteArray string = device->read( len ); - - if ( string.size() != len ) - return false; - - //string.replace( "\r", "\\r" ); - //string.replace( "\n", "\\n" ); - *static_cast( val.val.data ) = QString::fromLocal8Bit( string ); - } - return true; - case NifValue::tText: - { - int len; - device->read( (char *)&len, 4 ); - - if ( len > maxLength || len < 0 ) { - *static_cast( val.val.data ) = tr( "" ); return false; - } - - QByteArray string = device->read( len ); - - if ( string.size() != len ) - return false; - - *static_cast( val.val.data ) = QString( string ); - } - return true; - case NifValue::tByteArray: - { - int len; - device->read( (char *)&len, 4 ); - - if ( len < 0 ) - return false; - - *static_cast( val.val.data ) = device->read( len ); - return static_cast( val.val.data )->count() == len; - } - case NifValue::tStringPalette: - { - int len; - device->read( (char *)&len, 4 ); - - if ( len > 0xffff || len < 0 ) - return false; - - *static_cast( val.val.data ) = device->read( len ); - device->read( (char *)&len, 4 ); - return true; - } - case NifValue::tByteMatrix: - { - int len1, len2; - device->read( (char *)&len1, 4 ); - device->read( (char *)&len2, 4 ); - - if ( len1 < 0 || len2 < 0 ) - return false; - - int len = len1 * len2; - ByteMatrix tmp( len1, len2 ); - qint64 rlen = device->read( tmp.data(), len ); - tmp.swap( *static_cast( val.val.data ) ); - return (rlen == len); - } - case NifValue::tHeaderString: - { - QByteArray string; - int c = 0; - char chr = 0; - - while ( c++ < 80 && device->getChar( &chr ) && chr != '\n' ) - string.append( chr ); - - if ( c >= 80 ) - return false; - - *static_cast( val.val.data ) = QString( string ); - bool x = model->setHeaderString( QString( string ) ); - init(); - return x; - } - case NifValue::tLineString: - { - QByteArray string; - int c = 0; - char chr = 0; - - while ( c++ < 255 && device->getChar( &chr ) && chr != '\n' ) - string.append( chr ); - - if ( c >= 255 ) - return false; - - *static_cast( val.val.data ) = QString( string ); - return true; - } - case NifValue::tChar8String: - { - QByteArray string; - int c = 0; - char chr = 0; - - while ( c++ < 8 && device->getChar( &chr ) ) - string.append( chr ); - - if ( c > 9 ) - return false; - - *static_cast( val.val.data ) = QString( string ); - return true; - } - case NifValue::tFileVersion: - { - if ( device->read( (char *)&val.val.u32, 4 ) != 4 ) - return false; - - //bool x = model->setVersion( val.val.u32 ); - //init(); - if ( model->inherits( "NifModel" ) && model->getVersionNumber() >= 0x14000004 ) { - bool littleEndian; - device->peek( (char *)&littleEndian, 1 ); - bigEndian = !littleEndian; - - if ( bigEndian ) { - dataStream->setByteOrder( QDataStream::BigEndian ); - } - } - - // hack for neosteam - if ( val.val.u32 == 0x08F35232 ) { - val.val.u32 = 0x0a010000; - } - - return true; - } - case NifValue::tString: - { - if ( stringAdjust ) { - val.changeType( NifValue::tStringIndex ); - return device->read( (char *)&val.val.i32, 4 ) == 4; - } else { - val.changeType( NifValue::tSizedString ); - - int len; - device->read( (char *)&len, 4 ); - - if ( len > maxLength || len < 0 ) { - *static_cast( val.val.data ) = tr( "" ); return false; - } - - QByteArray string = device->read( len ); - - if ( string.size() != len ) - return false; - - //string.replace( "\r", "\\r" ); - //string.replace( "\n", "\\n" ); - *static_cast( val.val.data ) = QString( string ); - return true; - } - } - case NifValue::tFilePath: - { - if ( stringAdjust ) { - val.changeType( NifValue::tStringIndex ); - return device->read( (char *)&val.val.i32, 4 ) == 4; - } else { - val.changeType( NifValue::tSizedString ); - - int len; - device->read( (char *)&len, 4 ); - - if ( len > maxLength || len < 0 ) { - *static_cast( val.val.data ) = tr( "" ); return false; - } - - QByteArray string = device->read( len ); - - if ( string.size() != len ) - return false; - - *static_cast( val.val.data ) = QString( string ); - return true; - } - } - - case NifValue::tBlob: - { - if ( val.val.data ) { - QByteArray * array = static_cast( val.val.data ); - return device->read( array->data(), array->size() ) == array->size(); - } - - return false; - } - case NifValue::tNone: - return true; - } - - return false; -} - - -/* - * NifOStream - */ - -void NifOStream::init() -{ - bool32bit = (model->inherits( "NifModel" ) && model->getVersionNumber() <= 0x04000002); - linkAdjust = (model->inherits( "NifModel" ) && model->getVersionNumber() < 0x0303000D); - stringAdjust = (model->inherits( "NifModel" ) && model->getVersionNumber() >= 0x14010003); -} - -bool NifOStream::write( const NifValue & val ) -{ - switch ( val.type() ) { - case NifValue::tBool: - - if ( bool32bit ) - return device->write( (char *)&val.val.u32, 4 ) == 4; - else - return device->write( (char *)&val.val.u08, 1 ) == 1; - - case NifValue::tByte: - return device->write( (char *)&val.val.u08, 1 ) == 1; - case NifValue::tWord: - case NifValue::tShort: - case NifValue::tFlags: - case NifValue::tBlockTypeIndex: - return device->write( (char *)&val.val.u16, 2 ) == 2; - case NifValue::tStringOffset: - case NifValue::tInt: - case NifValue::tUInt: - case NifValue::tULittle32: - case NifValue::tStringIndex: - return device->write( (char *)&val.val.u32, 4 ) == 4; - case NifValue::tFileVersion: - { - if ( NifModel * mdl = static_cast( const_cast(model) ) ) { - QString headerString = mdl->getItem( mdl->getHeaderItem(), "Header String" )->value().toString(); - quint32 version; - - // hack for neosteam - if ( headerString.startsWith( "NS" ) ) { - version = 0x08F35232; - } else { - version = val.val.u32; - } - - return device->write( (char *)&version, 4 ) == 4; - } else { - return device->write( (char *)&val.val.u32, 4 ) == 4; - } - } - case NifValue::tLink: - case NifValue::tUpLink: - - if ( !linkAdjust ) { - return device->write( (char *)&val.val.i32, 4 ) == 4; - } else { - qint32 l = val.val.i32 + 1; - return device->write( (char *)&l, 4 ) == 4; - } - - case NifValue::tFloat: - return device->write( (char *)&val.val.f32, 4 ) == 4; - case NifValue::tHfloat: - { - uint16_t half = half_from_float( val.val.u32 ); - return device->write( (char *)&half, 2 ) == 2; - } - case NifValue::tByteVector3: - { - Vector3 * vec = static_cast(val.val.data); - if ( !vec ) - return false; - - uint8_t v[3]; - v[0] = round( ((vec->xyz[0] + 1.0) / 2.0) * 255.0 ); - v[1] = round( ((vec->xyz[1] + 1.0) / 2.0) * 255.0 ); - v[2] = round( ((vec->xyz[2] + 1.0) / 2.0) * 255.0 ); - - return device->write( (char*)v, 3 ) == 3; - } - case NifValue::tHalfVector3: - { - Vector3 * vec = static_cast(val.val.data); - if ( !vec ) - return false; - - union { float f; uint32_t i; } xu, yu, zu; - - xu.f = vec->xyz[0]; - yu.f = vec->xyz[1]; - zu.f = vec->xyz[2]; - - uint16_t v[3]; - v[0] = half_from_float( xu.i ); - v[1] = half_from_float( yu.i ); - v[2] = half_from_float( zu.i ); - - return device->write( (char*)v, 6 ) == 6; - } - case NifValue::tHalfVector2: - { - Vector2 * vec = static_cast(val.val.data); - if ( !vec ) - return false; - - union { float f; uint32_t i; } xu, yu; - - xu.f = vec->xy[0]; - yu.f = vec->xy[1]; - - uint16_t v[2]; - v[0] = half_from_float( xu.i ); - v[1] = half_from_float( yu.i ); - - return device->write( (char*)v, 4 ) == 4; - } - case NifValue::tVector3: - return device->write( (char *)static_cast( val.val.data )->xyz, 12 ) == 12; - case NifValue::tVector4: - return device->write( (char *)static_cast( val.val.data )->xyzw, 16 ) == 16; - case NifValue::tTriangle: - return device->write( (char *)static_cast( val.val.data )->v, 6 ) == 6; - case NifValue::tQuat: - return device->write( (char *)static_cast( val.val.data )->wxyz, 16 ) == 16; - case NifValue::tQuatXYZW: - { - Quat * q = static_cast( val.val.data ); - return device->write( (char *)&q->wxyz[1], 12 ) == 12 && device->write( (char *)q->wxyz, 4 ) == 4; - } - case NifValue::tMatrix: - return device->write( (char *)static_cast( val.val.data )->m, 36 ) == 36; - case NifValue::tMatrix4: - return device->write( (char *)static_cast( val.val.data )->m, 64 ) == 64; - case NifValue::tVector2: - return device->write( (char *)static_cast( val.val.data )->xy, 8 ) == 8; - case NifValue::tColor3: - return device->write( (char *)static_cast( val.val.data )->rgb, 12 ) == 12; - case NifValue::tByteColor4: - { - Color4 * color = static_cast(val.val.data); - if ( !color ) - return false; - - quint8 c[4]; - - auto cF = color->rgba; - for ( int i = 0; i < 4; i++ ) { - c[i] = round( cF[i] * 255.0f ); - } - - return device->write( (char*)c, 4 ) == 4; - } - case NifValue::tColor4: - return device->write( (char *)static_cast( val.val.data )->rgba, 16 ) == 16; - case NifValue::tSizedString: - { - QByteArray string = static_cast( val.val.data )->toLatin1(); - //string.replace( "\\r", "\r" ); - //string.replace( "\\n", "\n" ); - int len = string.size(); - - if ( device->write( (char *)&len, 4 ) != 4 ) - return false; - - return device->write( string.constData(), string.size() ) == string.size(); - } - case NifValue::tShortString: - { - QByteArray string = static_cast( val.val.data )->toLocal8Bit(); - string.replace( "\\r", "\r" ); - string.replace( "\\n", "\n" ); - - if ( string.size() > 254 ) - string.resize( 254 ); - - unsigned char len = string.size() + 1; - - if ( device->write( (char *)&len, 1 ) != 1 ) - return false; - - return device->write( string.constData(), len ) == len; - } - case NifValue::tText: - { - QByteArray string = static_cast( val.val.data )->toLatin1(); - int len = string.size(); - - if ( device->write( (char *)&len, 4 ) != 4 ) - return false; - - return device->write( (const char *)string.constData(), string.size() ) == string.size(); - } - case NifValue::tHeaderString: - case NifValue::tLineString: - { - QByteArray string = static_cast( val.val.data )->toLatin1(); - - if ( device->write( string.constData(), string.length() ) != string.length() ) - return false; - - return ( device->write( "\n", 1 ) == 1 ); - } - case NifValue::tChar8String: - { - QByteArray string = static_cast( val.val.data )->toLatin1(); - quint32 n = std::min( 8, string.length() ); - - if ( device->write( string.constData(), n ) != n ) - return false; - - for ( quint32 i = n; i < 8; ++i ) { - if ( device->write( "\0", 1 ) != 1 ) - return false; - } - - - return true; - } - case NifValue::tByteArray: - { - QByteArray * array = static_cast( val.val.data ); - int len = array->count(); - - if ( device->write( (char *)&len, 4 ) != 4 ) - return false; - - return device->write( *array ) == len; - } - case NifValue::tStringPalette: - { - QByteArray * array = static_cast( val.val.data ); - int len = array->count(); - - if ( device->write( (char *)&len, 4 ) != 4 ) - return false; - - if ( device->write( *array ) != len ) - return false; - - return device->write( (char *)&len, 4 ) == 4; - } - case NifValue::tByteMatrix: - { - ByteMatrix * array = static_cast( val.val.data ); - int len = array->count( 0 ); - - if ( device->write( (char *)&len, 4 ) != 4 ) - return false; - - len = array->count( 1 ); - - if ( device->write( (char *)&len, 4 ) != 4 ) - return false; - - len = array->count(); - return device->write( array->data(), len ) == len; - } - case NifValue::tString: - case NifValue::tFilePath: - { - if ( stringAdjust ) { - if ( val.val.u32 < 0x00010000 ) { - return device->write( (char *)&val.val.u32, 4 ) == 4; - } else { - int value = 0; - return device->write( (char *)&value, 4 ) == 4; - } - } else { - QByteArray string; - - if ( val.val.data != 0 ) { - string = static_cast( val.val.data )->toLatin1(); - } - - //string.replace( "\\r", "\r" ); - //string.replace( "\\n", "\n" ); - int len = string.size(); - - if ( device->write( (char *)&len, 4 ) != 4 ) - return false; - - return device->write( string.constData(), string.size() ) == string.size(); - } - } - case NifValue::tBlob: - - if ( val.val.data ) { - QByteArray * array = static_cast( val.val.data ); - return device->write( array->data(), array->size() ) == array->size(); - } - - return true; - - case NifValue::tNone: - return true; - } - - return false; -} - - -/* - * NifSStream - */ - -void NifSStream::init() -{ - bool32bit = ( model->inherits( "NifModel" ) && model->getVersionNumber() <= 0x04000002 ); - stringAdjust = ( model->inherits( "NifModel" ) && model->getVersionNumber() >= 0x14010003 ); -} - -int NifSStream::size( const NifValue & val ) -{ - switch ( val.type() ) { - case NifValue::tBool: - - if ( bool32bit ) - return 4; - else - return 1; - - case NifValue::tByte: - return 1; - case NifValue::tWord: - case NifValue::tShort: - case NifValue::tFlags: - case NifValue::tBlockTypeIndex: - return 2; - case NifValue::tStringOffset: - case NifValue::tInt: - case NifValue::tUInt: - case NifValue::tULittle32: - case NifValue::tStringIndex: - case NifValue::tFileVersion: - case NifValue::tLink: - case NifValue::tUpLink: - case NifValue::tFloat: - return 4; - case NifValue::tHfloat: - return 2; - case NifValue::tByteVector3: - return 3; - case NifValue::tHalfVector3: - return 6; - case NifValue::tHalfVector2: - return 4; - case NifValue::tVector3: - return 12; - case NifValue::tVector4: - return 16; - case NifValue::tTriangle: - return 6; - case NifValue::tQuat: - case NifValue::tQuatXYZW: - return 16; - case NifValue::tMatrix: - return 36; - case NifValue::tMatrix4: - return 64; - case NifValue::tVector2: - return 8; - case NifValue::tColor3: - return 12; - case NifValue::tByteColor4: - return 4; - case NifValue::tColor4: - return 16; - case NifValue::tSizedString: - { - QByteArray string = static_cast( val.val.data )->toLatin1(); - //string.replace( "\\r", "\r" ); - //string.replace( "\\n", "\n" ); - return 4 + string.size(); - } - case NifValue::tShortString: - { - QByteArray string = static_cast( val.val.data )->toLatin1(); - - //string.replace( "\\r", "\r" ); - //string.replace( "\\n", "\n" ); - if ( string.size() > 254 ) - string.resize( 254 ); - - return 1 + string.size() + 1; - } - case NifValue::tText: - { - QByteArray string = static_cast( val.val.data )->toLatin1(); - return 4 + string.size(); - } - case NifValue::tHeaderString: - case NifValue::tLineString: - { - QByteArray string = static_cast( val.val.data )->toLatin1(); - return string.length() + 1; - } - case NifValue::tChar8String: - { - return 8; - } - case NifValue::tByteArray: - { - QByteArray * array = static_cast( val.val.data ); - return 4 + array->count(); - } - case NifValue::tStringPalette: - { - QByteArray * array = static_cast( val.val.data ); - return 4 + array->count() + 4; - } - case NifValue::tByteMatrix: - { - ByteMatrix * array = static_cast( val.val.data ); - return 4 + 4 + array->count(); - } - case NifValue::tString: - case NifValue::tFilePath: - { - if ( stringAdjust ) { - return 4; - } - QByteArray string = static_cast( val.val.data )->toLatin1(); - //string.replace( "\\r", "\r" ); - //string.replace( "\\n", "\n" ); - return 4 + string.size(); - } - - case NifValue::tBlob: - - if ( val.val.data ) { - QByteArray * array = static_cast( val.val.data ); - return array->size(); - } - - return 0; - - case NifValue::tNone: - return 0; - } - - return 0; -} diff --git a/src/data/nifvalue.h b/src/data/nifvalue.h index 1e1a4c87b..77782832d 100644 --- a/src/data/nifvalue.h +++ b/src/data/nifvalue.h @@ -41,8 +41,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -#include - //! @file nifvalue.h NifValue, NifIStream, NifOStream, NifSStream @@ -399,104 +397,6 @@ class NifValue Q_DECLARE_METATYPE( NifValue ) -class BaseModel; -class NifItem; - -class QDataStream; -class QIODevice; - -//! An input stream that reads a file into a model. -class NifIStream final -{ - Q_DECLARE_TR_FUNCTIONS( NifIStream ) - -public: - NifIStream( BaseModel * m, QIODevice * d ) : model( m ), device( d ) - { - init(); - } - - //! Reads a NifValue from the underlying device. Returns true if successful. - bool read( NifValue & ); - -private: - //! The model that data is being read into. - BaseModel * model; - //! The underlying device that data is being read from. - QIODevice * device; - //! The data stream that is wrapped around the device (simplifies endian conversion) - std::unique_ptr dataStream; - - //! Initialises the stream. - void init(); - - //! Whether a boolean is 32-bit. - bool bool32bit = false; - //! Whether link adjustment is required. - bool linkAdjust = false; - //! Whether string adjustment is required. - bool stringAdjust = false; - //! Whether the model is big-endian - bool bigEndian = false; - - //! The maximum length of a string that can be read. - int maxLength = 0x8000; -}; - - -//! An output stream that writes a model to a file. -class NifOStream final -{ - Q_DECLARE_TR_FUNCTIONS( NifOStream ) - -public: - NifOStream( const BaseModel * n, QIODevice * d ) : model( n ), device( d ) { init(); } - - //! Writes a NifValue to the underlying device. Returns true if successful. - bool write( const NifValue & ); - -private: - //! The model that data is being read from. - const BaseModel * model; - //! The underlying device that data is being written to. - QIODevice * device; - - //! Initialises the stream. - void init(); - - //! Whether a boolean is 32-bit. - bool bool32bit = false; - //! Whether link adjustment is required. - bool linkAdjust = false; - //! Whether string adjustment is required. - bool stringAdjust = false; - //! Whether the model is big-endian - bool bigEndian = false; -}; - - -//! A stream that determines the size of values in a model. -class NifSStream final -{ -public: - NifSStream( const BaseModel * n ) : model( n ) { init(); } - - //! Determine the size of a given NifValue. - int size( const NifValue & ); - -private: - //! The model that values are being sized for. - const BaseModel * model; - - //! Initialises the stream. - void init(); - - //! Whether booleans are 32-bit or not. - bool bool32bit; - //! Whether string adjustment is required. - bool stringAdjust; -}; - // Inlines diff --git a/src/io/nifstream.cpp b/src/io/nifstream.cpp new file mode 100644 index 000000000..2d9a19f44 --- /dev/null +++ b/src/io/nifstream.cpp @@ -0,0 +1,917 @@ +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2015, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +3. The name of the NIF File Format Library and Tools project may not be +used to endorse or promote products derived from this software +without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +***** END LICENCE BLOCK *****/ + +#include "nifstream.h" +#include "nifmodel.h" +#include "half.h" + +#include +#include + + +/* +* NifIStream +*/ + +void NifIStream::init() +{ + bool32bit = (model->inherits( "NifModel" ) && model->getVersionNumber() <= 0x04000002); + linkAdjust = (model->inherits( "NifModel" ) && model->getVersionNumber() < 0x0303000D); + stringAdjust = (model->inherits( "NifModel" ) && model->getVersionNumber() >= 0x14010003); + bigEndian = false; // set when tFileVersion is read + + dataStream = std::unique_ptr( new QDataStream( device ) ); + dataStream->setByteOrder( QDataStream::LittleEndian ); + dataStream->setFloatingPointPrecision( QDataStream::SinglePrecision ); + + maxLength = 0x8000; +} + +bool NifIStream::read( NifValue & val ) +{ + switch ( val.type() ) { + case NifValue::tBool: + { + val.val.u32 = 0; + + if ( bool32bit ) + *dataStream >> val.val.u32; + else + *dataStream >> val.val.u08; + + return (dataStream->status() == QDataStream::Ok); + } + case NifValue::tByte: + { + val.val.u32 = 0; + *dataStream >> val.val.u08; + return (dataStream->status() == QDataStream::Ok); + } + case NifValue::tWord: + case NifValue::tShort: + case NifValue::tFlags: + case NifValue::tBlockTypeIndex: + { + val.val.u32 = 0; + *dataStream >> val.val.u16; + return (dataStream->status() == QDataStream::Ok); + } + case NifValue::tStringOffset: + case NifValue::tInt: + case NifValue::tUInt: + { + *dataStream >> val.val.u32; + return (dataStream->status() == QDataStream::Ok); + } + case NifValue::tULittle32: + { + if ( bigEndian ) + dataStream->setByteOrder( QDataStream::LittleEndian ); + + *dataStream >> val.val.u32; + + if ( bigEndian ) + dataStream->setByteOrder( QDataStream::BigEndian ); + + return (dataStream->status() == QDataStream::Ok); + } + case NifValue::tStringIndex: + { + *dataStream >> val.val.u32; + return (dataStream->status() == QDataStream::Ok); + } + case NifValue::tLink: + case NifValue::tUpLink: + { + *dataStream >> val.val.i32; + + if ( linkAdjust ) + val.val.i32--; + + return (dataStream->status() == QDataStream::Ok); + } + case NifValue::tFloat: + { + *dataStream >> val.val.f32; + return (dataStream->status() == QDataStream::Ok); + } + case NifValue::tHfloat: + { + uint16_t half; + *dataStream >> half; + val.val.u32 = half_to_float( half ); + return (dataStream->status() == QDataStream::Ok); + } + case NifValue::tByteVector3: + { + quint8 x, y, z; + float xf, yf, zf; + + *dataStream >> x; + *dataStream >> y; + *dataStream >> z; + + xf = (double( x ) / 255.0) * 2.0 - 1.0; + yf = (double( y ) / 255.0) * 2.0 - 1.0; + zf = (double( z ) / 255.0) * 2.0 - 1.0; + + Vector3 * v = static_cast(val.val.data); + v->xyz[0] = xf; v->xyz[1] = yf; v->xyz[2] = zf; + + return (dataStream->status() == QDataStream::Ok); + } + case NifValue::tHalfVector3: + { + uint16_t x, y, z; + union { float f; uint32_t i; } xu, yu, zu; + + *dataStream >> x; + *dataStream >> y; + *dataStream >> z; + + xu.i = half_to_float( x ); + yu.i = half_to_float( y ); + zu.i = half_to_float( z ); + + Vector3 * v = static_cast(val.val.data); + v->xyz[0] = xu.f; v->xyz[1] = yu.f; v->xyz[2] = zu.f; + + return (dataStream->status() == QDataStream::Ok); + } + case NifValue::tHalfVector2: + { + uint16_t x, y; + union { float f; uint32_t i; } xu, yu; + + *dataStream >> x; + *dataStream >> y; + + xu.i = half_to_float( x ); + yu.i = half_to_float( y ); + + Vector2 * v = static_cast(val.val.data); + v->xy[0] = xu.f; v->xy[1] = yu.f; + + return (dataStream->status() == QDataStream::Ok); + } + case NifValue::tVector3: + { + Vector3 * v = static_cast(val.val.data); + *dataStream >> *v; + return (dataStream->status() == QDataStream::Ok); + } + case NifValue::tVector4: + { + Vector4 * v = static_cast(val.val.data); + *dataStream >> *v; + return (dataStream->status() == QDataStream::Ok); + } + case NifValue::tTriangle: + { + Triangle * t = static_cast(val.val.data); + *dataStream >> *t; + return (dataStream->status() == QDataStream::Ok); + } + case NifValue::tQuat: + { + Quat * q = static_cast(val.val.data); + *dataStream >> *q; + return (dataStream->status() == QDataStream::Ok); + } + case NifValue::tQuatXYZW: + { + Quat * q = static_cast(val.val.data); + return device->read( (char *)&q->wxyz[1], 12 ) == 12 && device->read( (char *)q->wxyz, 4 ) == 4; + } + case NifValue::tMatrix: + return device->read( (char *)static_cast(val.val.data)->m, 36 ) == 36; + case NifValue::tMatrix4: + return device->read( (char *)static_cast(val.val.data)->m, 64 ) == 64; + case NifValue::tVector2: + { + Vector2 * v = static_cast(val.val.data); + *dataStream >> *v; + return (dataStream->status() == QDataStream::Ok); + } + case NifValue::tColor3: + return device->read( (char *)static_cast(val.val.data)->rgb, 12 ) == 12; + case NifValue::tByteColor4: + { + quint8 r, g, b, a; + *dataStream >> r; + *dataStream >> g; + *dataStream >> b; + *dataStream >> a; + + Color4 * c = static_cast(val.val.data); + c->setRGBA( (float)r / 255.0, (float)g / 255.0, (float)b / 255.0, (float)a / 255.0 ); + + return (dataStream->status() == QDataStream::Ok); + } + case NifValue::tColor4: + { + Color4 * c = static_cast(val.val.data); + *dataStream >> *c; + return (dataStream->status() == QDataStream::Ok); + } + case NifValue::tSizedString: + { + int len; + //device->read( (char *) &len, 4 ); + *dataStream >> len; + + if ( len > maxLength || len < 0 ) { + *static_cast(val.val.data) = tr( "" ).arg( len, 0, 16 ); return false; + } + + QByteArray string = device->read( len ); + + if ( string.size() != len ) + return false; + + //string.replace( "\r", "\\r" ); + //string.replace( "\n", "\\n" ); + *static_cast(val.val.data) = QString( string ); + } + return true; + case NifValue::tShortString: + { + unsigned char len; + device->read( (char *)&len, 1 ); + QByteArray string = device->read( len ); + + if ( string.size() != len ) + return false; + + //string.replace( "\r", "\\r" ); + //string.replace( "\n", "\\n" ); + *static_cast(val.val.data) = QString::fromLocal8Bit( string ); + } + return true; + case NifValue::tText: + { + int len; + device->read( (char *)&len, 4 ); + + if ( len > maxLength || len < 0 ) { + *static_cast(val.val.data) = tr( "" ); return false; + } + + QByteArray string = device->read( len ); + + if ( string.size() != len ) + return false; + + *static_cast(val.val.data) = QString( string ); + } + return true; + case NifValue::tByteArray: + { + int len; + device->read( (char *)&len, 4 ); + + if ( len < 0 ) + return false; + + *static_cast(val.val.data) = device->read( len ); + return static_cast(val.val.data)->count() == len; + } + case NifValue::tStringPalette: + { + int len; + device->read( (char *)&len, 4 ); + + if ( len > 0xffff || len < 0 ) + return false; + + *static_cast(val.val.data) = device->read( len ); + device->read( (char *)&len, 4 ); + return true; + } + case NifValue::tByteMatrix: + { + int len1, len2; + device->read( (char *)&len1, 4 ); + device->read( (char *)&len2, 4 ); + + if ( len1 < 0 || len2 < 0 ) + return false; + + int len = len1 * len2; + ByteMatrix tmp( len1, len2 ); + qint64 rlen = device->read( tmp.data(), len ); + tmp.swap( *static_cast(val.val.data) ); + return (rlen == len); + } + case NifValue::tHeaderString: + { + QByteArray string; + int c = 0; + char chr = 0; + + while ( c++ < 80 && device->getChar( &chr ) && chr != '\n' ) + string.append( chr ); + + if ( c >= 80 ) + return false; + + *static_cast(val.val.data) = QString( string ); + bool x = model->setHeaderString( QString( string ) ); + init(); + return x; + } + case NifValue::tLineString: + { + QByteArray string; + int c = 0; + char chr = 0; + + while ( c++ < 255 && device->getChar( &chr ) && chr != '\n' ) + string.append( chr ); + + if ( c >= 255 ) + return false; + + *static_cast(val.val.data) = QString( string ); + return true; + } + case NifValue::tChar8String: + { + QByteArray string; + int c = 0; + char chr = 0; + + while ( c++ < 8 && device->getChar( &chr ) ) + string.append( chr ); + + if ( c > 9 ) + return false; + + *static_cast(val.val.data) = QString( string ); + return true; + } + case NifValue::tFileVersion: + { + if ( device->read( (char *)&val.val.u32, 4 ) != 4 ) + return false; + + //bool x = model->setVersion( val.val.u32 ); + //init(); + if ( model->inherits( "NifModel" ) && model->getVersionNumber() >= 0x14000004 ) { + bool littleEndian; + device->peek( (char *)&littleEndian, 1 ); + bigEndian = !littleEndian; + + if ( bigEndian ) { + dataStream->setByteOrder( QDataStream::BigEndian ); + } + } + + // hack for neosteam + if ( val.val.u32 == 0x08F35232 ) { + val.val.u32 = 0x0a010000; + } + + return true; + } + case NifValue::tString: + { + if ( stringAdjust ) { + val.changeType( NifValue::tStringIndex ); + return device->read( (char *)&val.val.i32, 4 ) == 4; + } else { + val.changeType( NifValue::tSizedString ); + + int len; + device->read( (char *)&len, 4 ); + + if ( len > maxLength || len < 0 ) { + *static_cast(val.val.data) = tr( "" ); return false; + } + + QByteArray string = device->read( len ); + + if ( string.size() != len ) + return false; + + //string.replace( "\r", "\\r" ); + //string.replace( "\n", "\\n" ); + *static_cast(val.val.data) = QString( string ); + return true; + } + } + case NifValue::tFilePath: + { + if ( stringAdjust ) { + val.changeType( NifValue::tStringIndex ); + return device->read( (char *)&val.val.i32, 4 ) == 4; + } else { + val.changeType( NifValue::tSizedString ); + + int len; + device->read( (char *)&len, 4 ); + + if ( len > maxLength || len < 0 ) { + *static_cast(val.val.data) = tr( "" ); return false; + } + + QByteArray string = device->read( len ); + + if ( string.size() != len ) + return false; + + *static_cast(val.val.data) = QString( string ); + return true; + } + } + + case NifValue::tBlob: + { + if ( val.val.data ) { + QByteArray * array = static_cast(val.val.data); + return device->read( array->data(), array->size() ) == array->size(); + } + + return false; + } + case NifValue::tNone: + return true; + } + + return false; +} + + +/* +* NifOStream +*/ + +void NifOStream::init() +{ + bool32bit = (model->inherits( "NifModel" ) && model->getVersionNumber() <= 0x04000002); + linkAdjust = (model->inherits( "NifModel" ) && model->getVersionNumber() < 0x0303000D); + stringAdjust = (model->inherits( "NifModel" ) && model->getVersionNumber() >= 0x14010003); +} + +bool NifOStream::write( const NifValue & val ) +{ + switch ( val.type() ) { + case NifValue::tBool: + + if ( bool32bit ) + return device->write( (char *)&val.val.u32, 4 ) == 4; + else + return device->write( (char *)&val.val.u08, 1 ) == 1; + + case NifValue::tByte: + return device->write( (char *)&val.val.u08, 1 ) == 1; + case NifValue::tWord: + case NifValue::tShort: + case NifValue::tFlags: + case NifValue::tBlockTypeIndex: + return device->write( (char *)&val.val.u16, 2 ) == 2; + case NifValue::tStringOffset: + case NifValue::tInt: + case NifValue::tUInt: + case NifValue::tULittle32: + case NifValue::tStringIndex: + return device->write( (char *)&val.val.u32, 4 ) == 4; + case NifValue::tFileVersion: + { + if ( NifModel * mdl = static_cast(const_cast(model)) ) { + QString headerString = mdl->getItem( mdl->getHeaderItem(), "Header String" )->value().toString(); + quint32 version; + + // hack for neosteam + if ( headerString.startsWith( "NS" ) ) { + version = 0x08F35232; + } else { + version = val.val.u32; + } + + return device->write( (char *)&version, 4 ) == 4; + } else { + return device->write( (char *)&val.val.u32, 4 ) == 4; + } + } + case NifValue::tLink: + case NifValue::tUpLink: + + if ( !linkAdjust ) { + return device->write( (char *)&val.val.i32, 4 ) == 4; + } else { + qint32 l = val.val.i32 + 1; + return device->write( (char *)&l, 4 ) == 4; + } + + case NifValue::tFloat: + return device->write( (char *)&val.val.f32, 4 ) == 4; + case NifValue::tHfloat: + { + uint16_t half = half_from_float( val.val.u32 ); + return device->write( (char *)&half, 2 ) == 2; + } + case NifValue::tByteVector3: + { + Vector3 * vec = static_cast(val.val.data); + if ( !vec ) + return false; + + uint8_t v[3]; + v[0] = round( ((vec->xyz[0] + 1.0) / 2.0) * 255.0 ); + v[1] = round( ((vec->xyz[1] + 1.0) / 2.0) * 255.0 ); + v[2] = round( ((vec->xyz[2] + 1.0) / 2.0) * 255.0 ); + + return device->write( (char*)v, 3 ) == 3; + } + case NifValue::tHalfVector3: + { + Vector3 * vec = static_cast(val.val.data); + if ( !vec ) + return false; + + union { float f; uint32_t i; } xu, yu, zu; + + xu.f = vec->xyz[0]; + yu.f = vec->xyz[1]; + zu.f = vec->xyz[2]; + + uint16_t v[3]; + v[0] = half_from_float( xu.i ); + v[1] = half_from_float( yu.i ); + v[2] = half_from_float( zu.i ); + + return device->write( (char*)v, 6 ) == 6; + } + case NifValue::tHalfVector2: + { + Vector2 * vec = static_cast(val.val.data); + if ( !vec ) + return false; + + union { float f; uint32_t i; } xu, yu; + + xu.f = vec->xy[0]; + yu.f = vec->xy[1]; + + uint16_t v[2]; + v[0] = half_from_float( xu.i ); + v[1] = half_from_float( yu.i ); + + return device->write( (char*)v, 4 ) == 4; + } + case NifValue::tVector3: + return device->write( (char *)static_cast(val.val.data)->xyz, 12 ) == 12; + case NifValue::tVector4: + return device->write( (char *)static_cast(val.val.data)->xyzw, 16 ) == 16; + case NifValue::tTriangle: + return device->write( (char *)static_cast(val.val.data)->v, 6 ) == 6; + case NifValue::tQuat: + return device->write( (char *)static_cast(val.val.data)->wxyz, 16 ) == 16; + case NifValue::tQuatXYZW: + { + Quat * q = static_cast(val.val.data); + return device->write( (char *)&q->wxyz[1], 12 ) == 12 && device->write( (char *)q->wxyz, 4 ) == 4; + } + case NifValue::tMatrix: + return device->write( (char *)static_cast(val.val.data)->m, 36 ) == 36; + case NifValue::tMatrix4: + return device->write( (char *)static_cast(val.val.data)->m, 64 ) == 64; + case NifValue::tVector2: + return device->write( (char *)static_cast(val.val.data)->xy, 8 ) == 8; + case NifValue::tColor3: + return device->write( (char *)static_cast(val.val.data)->rgb, 12 ) == 12; + case NifValue::tByteColor4: + { + Color4 * color = static_cast(val.val.data); + if ( !color ) + return false; + + quint8 c[4]; + + auto cF = color->rgba; + for ( int i = 0; i < 4; i++ ) { + c[i] = round( cF[i] * 255.0f ); + } + + return device->write( (char*)c, 4 ) == 4; + } + case NifValue::tColor4: + return device->write( (char *)static_cast(val.val.data)->rgba, 16 ) == 16; + case NifValue::tSizedString: + { + QByteArray string = static_cast(val.val.data)->toLatin1(); + //string.replace( "\\r", "\r" ); + //string.replace( "\\n", "\n" ); + int len = string.size(); + + if ( device->write( (char *)&len, 4 ) != 4 ) + return false; + + return device->write( string.constData(), string.size() ) == string.size(); + } + case NifValue::tShortString: + { + QByteArray string = static_cast(val.val.data)->toLocal8Bit(); + string.replace( "\\r", "\r" ); + string.replace( "\\n", "\n" ); + + if ( string.size() > 254 ) + string.resize( 254 ); + + unsigned char len = string.size() + 1; + + if ( device->write( (char *)&len, 1 ) != 1 ) + return false; + + return device->write( string.constData(), len ) == len; + } + case NifValue::tText: + { + QByteArray string = static_cast(val.val.data)->toLatin1(); + int len = string.size(); + + if ( device->write( (char *)&len, 4 ) != 4 ) + return false; + + return device->write( (const char *)string.constData(), string.size() ) == string.size(); + } + case NifValue::tHeaderString: + case NifValue::tLineString: + { + QByteArray string = static_cast(val.val.data)->toLatin1(); + + if ( device->write( string.constData(), string.length() ) != string.length() ) + return false; + + return (device->write( "\n", 1 ) == 1); + } + case NifValue::tChar8String: + { + QByteArray string = static_cast(val.val.data)->toLatin1(); + quint32 n = std::min( 8, string.length() ); + + if ( device->write( string.constData(), n ) != n ) + return false; + + for ( quint32 i = n; i < 8; ++i ) { + if ( device->write( "\0", 1 ) != 1 ) + return false; + } + + + return true; + } + case NifValue::tByteArray: + { + QByteArray * array = static_cast(val.val.data); + int len = array->count(); + + if ( device->write( (char *)&len, 4 ) != 4 ) + return false; + + return device->write( *array ) == len; + } + case NifValue::tStringPalette: + { + QByteArray * array = static_cast(val.val.data); + int len = array->count(); + + if ( device->write( (char *)&len, 4 ) != 4 ) + return false; + + if ( device->write( *array ) != len ) + return false; + + return device->write( (char *)&len, 4 ) == 4; + } + case NifValue::tByteMatrix: + { + ByteMatrix * array = static_cast(val.val.data); + int len = array->count( 0 ); + + if ( device->write( (char *)&len, 4 ) != 4 ) + return false; + + len = array->count( 1 ); + + if ( device->write( (char *)&len, 4 ) != 4 ) + return false; + + len = array->count(); + return device->write( array->data(), len ) == len; + } + case NifValue::tString: + case NifValue::tFilePath: + { + if ( stringAdjust ) { + if ( val.val.u32 < 0x00010000 ) { + return device->write( (char *)&val.val.u32, 4 ) == 4; + } else { + int value = 0; + return device->write( (char *)&value, 4 ) == 4; + } + } else { + QByteArray string; + + if ( val.val.data != 0 ) { + string = static_cast(val.val.data)->toLatin1(); + } + + //string.replace( "\\r", "\r" ); + //string.replace( "\\n", "\n" ); + int len = string.size(); + + if ( device->write( (char *)&len, 4 ) != 4 ) + return false; + + return device->write( string.constData(), string.size() ) == string.size(); + } + } + case NifValue::tBlob: + + if ( val.val.data ) { + QByteArray * array = static_cast(val.val.data); + return device->write( array->data(), array->size() ) == array->size(); + } + + return true; + + case NifValue::tNone: + return true; + } + + return false; +} + + +/* +* NifSStream +*/ + +void NifSStream::init() +{ + bool32bit = (model->inherits( "NifModel" ) && model->getVersionNumber() <= 0x04000002); + stringAdjust = (model->inherits( "NifModel" ) && model->getVersionNumber() >= 0x14010003); +} + +int NifSStream::size( const NifValue & val ) +{ + switch ( val.type() ) { + case NifValue::tBool: + + if ( bool32bit ) + return 4; + else + return 1; + + case NifValue::tByte: + return 1; + case NifValue::tWord: + case NifValue::tShort: + case NifValue::tFlags: + case NifValue::tBlockTypeIndex: + return 2; + case NifValue::tStringOffset: + case NifValue::tInt: + case NifValue::tUInt: + case NifValue::tULittle32: + case NifValue::tStringIndex: + case NifValue::tFileVersion: + case NifValue::tLink: + case NifValue::tUpLink: + case NifValue::tFloat: + return 4; + case NifValue::tHfloat: + return 2; + case NifValue::tByteVector3: + return 3; + case NifValue::tHalfVector3: + return 6; + case NifValue::tHalfVector2: + return 4; + case NifValue::tVector3: + return 12; + case NifValue::tVector4: + return 16; + case NifValue::tTriangle: + return 6; + case NifValue::tQuat: + case NifValue::tQuatXYZW: + return 16; + case NifValue::tMatrix: + return 36; + case NifValue::tMatrix4: + return 64; + case NifValue::tVector2: + return 8; + case NifValue::tColor3: + return 12; + case NifValue::tByteColor4: + return 4; + case NifValue::tColor4: + return 16; + case NifValue::tSizedString: + { + QByteArray string = static_cast(val.val.data)->toLatin1(); + //string.replace( "\\r", "\r" ); + //string.replace( "\\n", "\n" ); + return 4 + string.size(); + } + case NifValue::tShortString: + { + QByteArray string = static_cast(val.val.data)->toLatin1(); + + //string.replace( "\\r", "\r" ); + //string.replace( "\\n", "\n" ); + if ( string.size() > 254 ) + string.resize( 254 ); + + return 1 + string.size() + 1; + } + case NifValue::tText: + { + QByteArray string = static_cast(val.val.data)->toLatin1(); + return 4 + string.size(); + } + case NifValue::tHeaderString: + case NifValue::tLineString: + { + QByteArray string = static_cast(val.val.data)->toLatin1(); + return string.length() + 1; + } + case NifValue::tChar8String: + { + return 8; + } + case NifValue::tByteArray: + { + QByteArray * array = static_cast(val.val.data); + return 4 + array->count(); + } + case NifValue::tStringPalette: + { + QByteArray * array = static_cast(val.val.data); + return 4 + array->count() + 4; + } + case NifValue::tByteMatrix: + { + ByteMatrix * array = static_cast(val.val.data); + return 4 + 4 + array->count(); + } + case NifValue::tString: + case NifValue::tFilePath: + { + if ( stringAdjust ) { + return 4; + } + QByteArray string = static_cast(val.val.data)->toLatin1(); + //string.replace( "\\r", "\r" ); + //string.replace( "\\n", "\n" ); + return 4 + string.size(); + } + + case NifValue::tBlob: + + if ( val.val.data ) { + QByteArray * array = static_cast(val.val.data); + return array->size(); + } + + return 0; + + case NifValue::tNone: + return 0; + } + + return 0; +} diff --git a/src/io/nifstream.h b/src/io/nifstream.h new file mode 100644 index 000000000..cba938f4a --- /dev/null +++ b/src/io/nifstream.h @@ -0,0 +1,142 @@ +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2015, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +3. The name of the NIF File Format Library and Tools project may not be +used to endorse or promote products derived from this software +without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +***** END LICENCE BLOCK *****/ + +#ifndef NIFSTREAM_H +#define NIFSTREAM_H + +#include "nifvalue.h" + +#include + +#include + + +class BaseModel; +class NifItem; + +class QDataStream; +class QIODevice; + + +//! An input stream that reads a file into a model. +class NifIStream final +{ + Q_DECLARE_TR_FUNCTIONS( NifIStream ) + +public: + NifIStream( BaseModel * m, QIODevice * d ) : model( m ), device( d ) + { + init(); + } + + //! Reads a NifValue from the underlying device. Returns true if successful. + bool read( NifValue & ); + +private: + //! The model that data is being read into. + BaseModel * model; + //! The underlying device that data is being read from. + QIODevice * device; + //! The data stream that is wrapped around the device (simplifies endian conversion) + std::unique_ptr dataStream; + + //! Initialises the stream. + void init(); + + //! Whether a boolean is 32-bit. + bool bool32bit = false; + //! Whether link adjustment is required. + bool linkAdjust = false; + //! Whether string adjustment is required. + bool stringAdjust = false; + //! Whether the model is big-endian + bool bigEndian = false; + + //! The maximum length of a string that can be read. + int maxLength = 0x8000; +}; + + +//! An output stream that writes a model to a file. +class NifOStream final +{ + Q_DECLARE_TR_FUNCTIONS( NifOStream ) + +public: + NifOStream( const BaseModel * n, QIODevice * d ) : model( n ), device( d ) { init(); } + + //! Writes a NifValue to the underlying device. Returns true if successful. + bool write( const NifValue & ); + +private: + //! The model that data is being read from. + const BaseModel * model; + //! The underlying device that data is being written to. + QIODevice * device; + + //! Initialises the stream. + void init(); + + //! Whether a boolean is 32-bit. + bool bool32bit = false; + //! Whether link adjustment is required. + bool linkAdjust = false; + //! Whether string adjustment is required. + bool stringAdjust = false; + //! Whether the model is big-endian + bool bigEndian = false; +}; + + +//! A stream that determines the size of values in a model. +class NifSStream final +{ +public: + NifSStream( const BaseModel * n ) : model( n ) { init(); } + + //! Determine the size of a given NifValue. + int size( const NifValue & ); + +private: + //! The model that values are being sized for. + const BaseModel * model; + + //! Initialises the stream. + void init(); + + //! Whether booleans are 32-bit or not. + bool bool32bit; + //! Whether string adjustment is required. + bool stringAdjust; +}; + +#endif diff --git a/src/model/kfmmodel.cpp b/src/model/kfmmodel.cpp index 21fb19cc2..d2eb5fcce 100644 --- a/src/model/kfmmodel.cpp +++ b/src/model/kfmmodel.cpp @@ -31,6 +31,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "kfmmodel.h" +#include "nifstream.h" //! @file kfmmodel.cpp KfmModel diff --git a/src/model/nifmodel.cpp b/src/model/nifmodel.cpp index d0b13cb00..58e16378b 100644 --- a/src/model/nifmodel.cpp +++ b/src/model/nifmodel.cpp @@ -31,6 +31,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "nifmodel.h" +#include "nifstream.h" #include "niftypes.h" #include "spellbook.h" From 23e97ff3db2d38d17616d79588745bf520c88872 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Fri, 23 Jun 2017 12:52:15 -0400 Subject: [PATCH 042/152] [Reorg] Split main() into own file --- NifSkope.pro | 1 + src/main.cpp | 256 +++++++++++++++++++++++++++++++++++++++++++++++ src/nifskope.cpp | 239 +------------------------------------------ src/nifskope.h | 5 + 4 files changed, 264 insertions(+), 237 deletions(-) create mode 100644 src/main.cpp diff --git a/NifSkope.pro b/NifSkope.pro index 46dd65a99..346ff5ca1 100644 --- a/NifSkope.pro +++ b/NifSkope.pro @@ -291,6 +291,7 @@ SOURCES += \ src/xml/nifexpr.cpp \ src/xml/nifxml.cpp \ src/glview.cpp \ + src/main.cpp \ src/message.cpp \ src/nifskope.cpp \ src/nifskope_ui.cpp \ diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 000000000..3872c3beb --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,256 @@ +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2015, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the NIF File Format Library and Tools project may not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +***** END LICENCE BLOCK *****/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "nifskope.h" +#include "nifvalue.h" +#include "nifmodel.h" +#include "kfmmodel.h" +#include "version.h" + + +QCoreApplication * createApplication( int &argc, char *argv[] ) +{ + // Iterate over args + for ( int i = 1; i < argc; ++i ) { + // -no-gui: start as core app without all the GUI overhead + if ( !qstrcmp( argv[i], "-no-gui" ) ) { + return new QCoreApplication( argc, argv ); + } + } + return new QApplication( argc, argv ); +} + + +/* + * main + */ + +//! The main program +int main( int argc, char * argv[] ) +{ + QScopedPointer app( createApplication( argc, argv ) ); + + if ( auto a = qobject_cast(app.data()) ) { + + a->setOrganizationName( "NifTools" ); + a->setOrganizationDomain( "niftools.org" ); + a->setApplicationName( "NifSkope " + NifSkopeVersion::rawToMajMin( NIFSKOPE_VERSION ) ); + a->setApplicationVersion( NIFSKOPE_VERSION ); + a->setApplicationDisplayName( "NifSkope " + NifSkopeVersion::rawToDisplay( NIFSKOPE_VERSION, true ) ); + + // Must set current directory or this causes issues with several features + QDir::setCurrent( qApp->applicationDirPath() ); + + // Register message handler + //qRegisterMetaType( "Message" ); + qInstallMessageHandler( NifSkope::MessageOutput ); + + // Register types + qRegisterMetaType( "NifValue" ); + QMetaType::registerComparators(); + + // Find stylesheet + QDir qssDir( QApplication::applicationDirPath() ); + QStringList qssList( QStringList() + << "style.qss" +#ifdef Q_OS_LINUX + << "/usr/share/nifskope/style.qss" +#endif + ); + QString qssName; + for ( const QString& str : qssList ) { + if ( qssDir.exists( str ) ) { + qssName = qssDir.filePath( str ); + break; + } + } + + // Load stylesheet + if ( !qssName.isEmpty() ) { + QFile style( qssName ); + + if ( style.open( QFile::ReadOnly ) ) { + a->setStyleSheet( style.readAll() ); + style.close(); + } + } + + // Set locale + QSettings cfg( QString( "%1/nifskope.ini" ).arg( QCoreApplication::applicationDirPath() ), QSettings::IniFormat ); + cfg.beginGroup( "Settings" ); + NifSkope::SetAppLocale( cfg.value( "Locale", "en" ).toLocale() ); + cfg.endGroup(); + + // Load XML files + NifModel::loadXML(); + KfmModel::loadXML(); + + int port = NIFSKOPE_IPC_PORT; + + QStack fnames; + + // Command Line setup + QCommandLineParser parser; + parser.addHelpOption(); + parser.addVersionOption(); + + // Add port option + QCommandLineOption portOption( {"p", "port"}, "Port NifSkope listens on", "port" ); + parser.addOption( portOption ); + + // Process options + parser.process( *a ); + + // Override port value + if ( parser.isSet( portOption ) ) + port = parser.value( portOption ).toInt(); + + // Files were passed to NifSkope + for ( const QString & arg : parser.positionalArguments() ) { + QString fname = QDir::current().filePath( arg ); + + if ( QFileInfo( fname ).exists() ) { + fnames.push( fname ); + } + } + + // No files were passed to NifSkope, push empty string + if ( fnames.isEmpty() ) { + fnames.push( QString() ); + } + + if ( IPCsocket * ipc = IPCsocket::create( port ) ) { + //qDebug() << "IPCSocket exec"; + ipc->execCommand( QString( "NifSkope::open %1" ).arg( fnames.pop() ) ); + + while ( !fnames.isEmpty() ) { + IPCsocket::sendCommand( QString( "NifSkope::open %1" ).arg( fnames.pop() ), port ); + } + + return a->exec(); + } else { + //qDebug() << "IPCSocket send"; + while ( !fnames.isEmpty() ) { + IPCsocket::sendCommand( QString( "NifSkope::open %1" ).arg( fnames.pop() ), port ); + } + return 0; + } + } else { + // Future command line batch tools here + } + + return 0; +} + + + +/* +* IPC socket +*/ + +IPCsocket * IPCsocket::create( int port ) +{ + QUdpSocket * udp = new QUdpSocket(); + + if ( udp->bind( QHostAddress( QHostAddress::LocalHost ), port, QUdpSocket::DontShareAddress ) ) { + IPCsocket * ipc = new IPCsocket( udp ); + QDesktopServices::setUrlHandler( "nif", ipc, "openNif" ); + return ipc; + } + + return nullptr; +} + +void IPCsocket::sendCommand( const QString & cmd, int port ) +{ + QUdpSocket udp; + udp.writeDatagram( (const char *)cmd.data(), cmd.length() * sizeof( QChar ), QHostAddress( QHostAddress::LocalHost ), port ); +} + +IPCsocket::IPCsocket( QUdpSocket * s ) : QObject(), socket( s ) +{ + QObject::connect( socket, &QUdpSocket::readyRead, this, &IPCsocket::processDatagram ); +} + +IPCsocket::~IPCsocket() +{ + delete socket; +} + +void IPCsocket::processDatagram() +{ + while ( socket->hasPendingDatagrams() ) { + QByteArray data; + data.resize( socket->pendingDatagramSize() ); + QHostAddress host; + quint16 port = 0; + + socket->readDatagram( data.data(), data.size(), &host, &port ); + + if ( host == QHostAddress( QHostAddress::LocalHost ) && (data.size() % sizeof( QChar )) == 0 ) { + QString cmd; + cmd.setUnicode( (QChar *)data.data(), data.size() / sizeof( QChar ) ); + execCommand( cmd ); + } + } +} + +void IPCsocket::execCommand( const QString & cmd ) +{ + if ( cmd.startsWith( "NifSkope::open" ) ) { + openNif( cmd.right( cmd.length() - 15 ) ); + } +} + +void IPCsocket::openNif( const QUrl & url ) +{ + auto file = url.toString(); + file.remove( 0, 4 ); + + openNif( file ); +} + +void IPCsocket::openNif( const QString & url ) +{ + NifSkope::createWindow( url ); +} diff --git a/src/nifskope.cpp b/src/nifskope.cpp index db258645a..a3e2d8fa8 100644 --- a/src/nifskope.cpp +++ b/src/nifskope.cpp @@ -54,22 +54,16 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include #include #include #include #include #include -#include -#include #include #include #include #include -#include -#include #include -#include #include #include @@ -1069,7 +1063,7 @@ void SelectIndexCommand::undo() //! Application-wide debug and warning message handler -void myMessageOutput( QtMsgType type, const QMessageLogContext & context, const QString & str ) +void NifSkope::MessageOutput( QtMsgType type, const QMessageLogContext & context, const QString & str ) { switch ( type ) { case QtDebugMsg: @@ -1093,105 +1087,10 @@ void myMessageOutput( QtMsgType type, const QMessageLogContext & context, const } -/* - * IPC socket - */ - -IPCsocket * IPCsocket::create( int port ) -{ - QUdpSocket * udp = new QUdpSocket(); - - if ( udp->bind( QHostAddress( QHostAddress::LocalHost ), port, QUdpSocket::DontShareAddress ) ) { - IPCsocket * ipc = new IPCsocket( udp ); - QDesktopServices::setUrlHandler( "nif", ipc, "openNif" ); - return ipc; - } - - return nullptr; -} - -void IPCsocket::sendCommand( const QString & cmd, int port ) -{ - QUdpSocket udp; - udp.writeDatagram( (const char *)cmd.data(), cmd.length() * sizeof( QChar ), QHostAddress( QHostAddress::LocalHost ), port ); -} - -IPCsocket::IPCsocket( QUdpSocket * s ) : QObject(), socket( s ) -{ - QObject::connect( socket, &QUdpSocket::readyRead, this, &IPCsocket::processDatagram ); -} - -IPCsocket::~IPCsocket() -{ - delete socket; -} - -void IPCsocket::processDatagram() -{ - while ( socket->hasPendingDatagrams() ) { - QByteArray data; - data.resize( socket->pendingDatagramSize() ); - QHostAddress host; - quint16 port = 0; - - socket->readDatagram( data.data(), data.size(), &host, &port ); - - if ( host == QHostAddress( QHostAddress::LocalHost ) && ( data.size() % sizeof( QChar ) ) == 0 ) { - QString cmd; - cmd.setUnicode( (QChar *)data.data(), data.size() / sizeof( QChar ) ); - execCommand( cmd ); - } - } -} - -void IPCsocket::execCommand( const QString & cmd ) -{ - if ( cmd.startsWith( "NifSkope::open" ) ) { - openNif( cmd.right( cmd.length() - 15 ) ); - } -} - -void IPCsocket::openNif( const QUrl & url ) -{ - auto file = url.toString(); - file.remove( 0, 4 ); - - openNif( file ); -} - -void IPCsocket::openNif( const QString & url ) -{ - NifSkope::createWindow( url ); -} - - -// TODO: This class was not used. QSystemLocale became private in Qt 5. -// It appears this class was going to handle display of numbers. -//! System locale override -/** - * Qt does not use the System Locale consistency so this basically forces all floating - * numbers into C format but leaves all other local specific settings. - */ -/*class NifSystemLocale : QSystemLocale -{ - virtual QVariant query(QueryType type, QVariant in) const - { - switch (type) - { - case DecimalPoint: - return QVariant( QLocale::c().decimalPoint() ); - case GroupSeparator: - return QVariant( QLocale::c().groupSeparator() ); - default: - return QVariant(); - } - } -};*/ - static QTranslator * mTranslator = nullptr; //! Sets application locale and loads translation files -static void SetAppLocale( QLocale curLocale ) +void NifSkope::SetAppLocale( QLocale curLocale ) { QDir directory( QApplication::applicationDirPath() ); @@ -1240,140 +1139,6 @@ void NifSkope::sltLocaleChanged() //ui->retranslateUi( this ); } -QCoreApplication * createApplication( int &argc, char *argv[] ) -{ - // Iterate over args - for ( int i = 1; i < argc; ++i ) { - // -no-gui: start as core app without all the GUI overhead - if ( !qstrcmp( argv[i], "-no-gui" ) ) { - return new QCoreApplication( argc, argv ); - } - } - return new QApplication( argc, argv ); -} - - -/* - * main - */ - -//! The main program -int main( int argc, char * argv[] ) -{ - QScopedPointer app( createApplication( argc, argv ) ); - - if ( auto a = qobject_cast(app.data()) ) { - - a->setOrganizationName( "NifTools" ); - a->setOrganizationDomain( "niftools.org" ); - a->setApplicationName( "NifSkope " + NifSkopeVersion::rawToMajMin( NIFSKOPE_VERSION ) ); - a->setApplicationVersion( NIFSKOPE_VERSION ); - a->setApplicationDisplayName( "NifSkope " + NifSkopeVersion::rawToDisplay( NIFSKOPE_VERSION, true ) ); - - // Must set current directory or this causes issues with several features - QDir::setCurrent( qApp->applicationDirPath() ); - - // Register message handler - //qRegisterMetaType( "Message" ); - qInstallMessageHandler( myMessageOutput ); - - // Register types - qRegisterMetaType( "NifValue" ); - QMetaType::registerComparators(); - - // Find stylesheet - QDir qssDir( QApplication::applicationDirPath() ); - QStringList qssList( QStringList() - << "style.qss" -#ifdef Q_OS_LINUX - << "/usr/share/nifskope/style.qss" -#endif - ); - QString qssName; - for ( const QString& str : qssList ) { - if ( qssDir.exists( str ) ) { - qssName = qssDir.filePath( str ); - break; - } - } - - // Load stylesheet - if ( !qssName.isEmpty() ) { - QFile style( qssName ); - - if ( style.open( QFile::ReadOnly ) ) { - a->setStyleSheet( style.readAll() ); - style.close(); - } - } - - // Set locale - QSettings cfg; - cfg.beginGroup( "Settings" ); - SetAppLocale( cfg.value( "Locale", "en" ).toLocale() ); - cfg.endGroup(); - - // Load XML files - NifModel::loadXML(); - KfmModel::loadXML(); - - int port = NIFSKOPE_IPC_PORT; - - QStack fnames; - - // Command Line setup - QCommandLineParser parser; - parser.addHelpOption(); - parser.addVersionOption(); - - // Add port option - QCommandLineOption portOption( {"p", "port"}, "Port NifSkope listens on", "port" ); - parser.addOption( portOption ); - - // Process options - parser.process( *a ); - - // Override port value - if ( parser.isSet( portOption ) ) - port = parser.value( portOption ).toInt(); - - // Files were passed to NifSkope - for ( const QString & arg : parser.positionalArguments() ) { - QString fname = QDir::current().filePath( arg ); - - if ( QFileInfo( fname ).exists() ) { - fnames.push( fname ); - } - } - - // No files were passed to NifSkope, push empty string - if ( fnames.isEmpty() ) { - fnames.push( QString() ); - } - - if ( IPCsocket * ipc = IPCsocket::create( port ) ) { - //qDebug() << "IPCSocket exec"; - ipc->execCommand( QString( "NifSkope::open %1" ).arg( fnames.pop() ) ); - - while ( !fnames.isEmpty() ) { - IPCsocket::sendCommand( QString( "NifSkope::open %1" ).arg( fnames.pop() ), port ); - } - - return a->exec(); - } else { - //qDebug() << "IPCSocket send"; - while ( !fnames.isEmpty() ) { - IPCsocket::sendCommand( QString( "NifSkope::open %1" ).arg( fnames.pop() ), port ); - } - return 0; - } - } else { - // Future command line batch tools here - } - - return 0; -} - void NifSkope::migrateSettings() const { diff --git a/src/nifskope.h b/src/nifskope.h index 7de2ba5f9..8470b7e5d 100644 --- a/src/nifskope.h +++ b/src/nifskope.h @@ -139,6 +139,11 @@ class NifSkope final : public QMainWindow */ static QString fileFilters( bool allFiles = true ); + //! Sets application locale and loads translation files + static void SetAppLocale( QLocale curLocale ); + //! Application-wide debug and warning message handler + static void MessageOutput( QtMsgType type, const QMessageLogContext & context, const QString & str ); + //! A map of all the currently support filetypes to their file extensions. static const QList> filetypes; From a7e7e451341a8c0ba22081924f8dd638da98438a Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sat, 24 Jun 2017 03:17:28 -0400 Subject: [PATCH 043/152] [Reorg] Rename expression class to match filename --- src/data/nifitem.h | 24 +++++------ src/model/basemodel.cpp | 2 +- src/model/basemodel.h | 2 +- src/xml/nifexpr.cpp | 96 ++++++++++++++++++++--------------------- src/xml/nifexpr.h | 54 +++++++++++------------ 5 files changed, 89 insertions(+), 89 deletions(-) diff --git a/src/data/nifitem.h b/src/data/nifitem.h index 168a7d13c..d2e7068ec 100644 --- a/src/data/nifitem.h +++ b/src/data/nifitem.h @@ -107,13 +107,13 @@ class NifSharedData final : public QSharedData //! Description text. QString text; //! Condition as an expression. - Expression condexpr; + NifExpr condexpr; //! First array length as an expression. - Expression arr1expr; + NifExpr arr1expr; //! Version condition. QString vercond; //! Version condition as an expression. - Expression verexpr; + NifExpr verexpr; DataFlags flags = None; }; @@ -157,13 +157,13 @@ class NifData //! Get the text description of the data. inline const QString & text() const { return d->text; } //! Get the condition attribute of the data, as an expression. - inline const Expression & condexpr() const { return d->condexpr; } + inline const NifExpr & condexpr() const { return d->condexpr; } //! Get the first array length of the data, as an expression. - inline const Expression & arr1expr() const { return d->arr1expr; } + inline const NifExpr & arr1expr() const { return d->arr1expr; } //! Get the version condition attribute of the data. inline const QString & vercond() const { return d->vercond; } //! Get the version condition attribute of the data, as an expression. - inline const Expression & verexpr() const { return d->verexpr; } + inline const NifExpr & verexpr() const { return d->verexpr; } //! Get the abstract attribute of the data. inline bool isAbstract() const { return d->flags & NifSharedData::Abstract; } //! Is the data binary. Binary means the data is being treated as one blob. @@ -193,7 +193,7 @@ class NifData void setArr1( const QString & arr1 ) { d->arr1 = arr1; - d->arr1expr = Expression( arr1 ); + d->arr1expr = NifExpr( arr1 ); } //! Sets the second array length of the data. void setArr2( const QString & arr2 ) { d->arr2 = arr2; } @@ -201,7 +201,7 @@ class NifData void setCond( const QString & cond ) { d->cond = cond; - d->condexpr = Expression( cond ); + d->condexpr = NifExpr( cond ); } //! Sets the earliest version of the data. void setVer1( quint32 ver1 ) { d->ver1 = ver1; } @@ -213,7 +213,7 @@ class NifData void setVerCond( const QString & cond ) { d->vercond = cond; - d->verexpr = Expression( cond ); + d->verexpr = NifExpr( cond ); } inline void setFlag( NifSharedData::DataFlags flag, bool val ) @@ -617,13 +617,13 @@ class NifItem inline QString text() const { return itemData.text(); } //! Return the condition attribute of the data, as an expression - inline const Expression & condexpr() const { return itemData.condexpr(); } + inline const NifExpr & condexpr() const { return itemData.condexpr(); } //! Return the arr1 attribute of the data, as an expression - inline const Expression & arr1expr() const { return itemData.arr1expr(); } + inline const NifExpr & arr1expr() const { return itemData.arr1expr(); } //! Return the version condition attribute of the data inline QString vercond() const { return itemData.vercond(); } //! Return the version condition attribute of the data, as an expression - inline const Expression & verexpr() const { return itemData.verexpr(); } + inline const NifExpr & verexpr() const { return itemData.verexpr(); } //! Return the abstract attribute of the data inline bool isAbstract() const { return itemData.isAbstract(); } //! Is the item data binary. Binary means the data is being treated as one blob. diff --git a/src/model/basemodel.cpp b/src/model/basemodel.cpp index 9afeb9140..2053b682b 100644 --- a/src/model/basemodel.cpp +++ b/src/model/basemodel.cpp @@ -735,7 +735,7 @@ QModelIndex BaseModel::getIndex( const QModelIndex & parent, const QString & nam * conditions and version */ -int BaseModel::evaluateInt( NifItem * item, const Expression & expr ) const +int BaseModel::evaluateInt( NifItem * item, const NifExpr & expr ) const { if ( !item || item == root ) return -1; diff --git a/src/model/basemodel.h b/src/model/basemodel.h index a4a71e30a..4e25dd225 100644 --- a/src/model/basemodel.h +++ b/src/model/basemodel.h @@ -334,7 +334,7 @@ class BaseModel : public QAbstractItemModel //! Get the size of an array int getArraySize( NifItem * array ) const; //! Evaluate a string for an array - int evaluateInt( NifItem * item, const Expression & expr ) const; + int evaluateInt( NifItem * item, const NifExpr & expr ) const; //! Get an item by name NifItem * getItemX( NifItem * item, const QString & name ) const; diff --git a/src/xml/nifexpr.cpp b/src/xml/nifexpr.cpp index 03be9b837..70108aec7 100644 --- a/src/xml/nifexpr.cpp +++ b/src/xml/nifexpr.cpp @@ -116,48 +116,48 @@ static quint32 version2number( const QString & s ) return ( i == 0xffffffff ? 0 : i ); } -Expression::Operator Expression::operatorFromString( const QString & str ) +NifExpr::Operator NifExpr::operatorFromString( const QString & str ) { if ( str == "!" ) - return Expression::e_not; + return NifExpr::e_not; else if ( str == "!=" ) - return Expression::e_not_eq; + return NifExpr::e_not_eq; else if ( str == "==" ) - return Expression::e_eq; + return NifExpr::e_eq; else if ( str == ">=" ) - return Expression::e_gte; + return NifExpr::e_gte; else if ( str == "<=" ) - return Expression::e_lte; + return NifExpr::e_lte; else if ( str == ">" ) - return Expression::e_gt; + return NifExpr::e_gt; else if ( str == "<" ) - return Expression::e_lt; + return NifExpr::e_lt; else if ( str == "&" ) - return Expression::e_bit_and; + return NifExpr::e_bit_and; else if ( str == "|" ) - return Expression::e_bit_or; + return NifExpr::e_bit_or; else if ( str == "+" ) - return Expression::e_add; + return NifExpr::e_add; else if ( str == "-" ) - return Expression::e_sub; + return NifExpr::e_sub; else if ( str == "/" ) - return Expression::e_div; + return NifExpr::e_div; else if ( str == "*" ) - return Expression::e_mul; + return NifExpr::e_mul; else if ( str == "&&" ) - return Expression::e_bool_and; + return NifExpr::e_bool_and; else if ( str == "||" ) - return Expression::e_bool_or; + return NifExpr::e_bool_or; - return Expression::e_nop; + return NifExpr::e_nop; } -void Expression::partition( const QString & cond, int offset /*= 0*/ ) +void NifExpr::partition( const QString & cond, int offset /*= 0*/ ) { int pos; if ( cond.isEmpty() ) { - opcode = Expression::e_nop; + opcode = NifExpr::e_nop; return; } @@ -166,8 +166,8 @@ void Expression::partition( const QString & cond, int offset /*= 0*/ ) QRegularExpressionMatch reUnaryMatch = reUnary.match( cond, offset ); pos = reUnaryMatch.capturedStart(); if ( pos != -1 ) { - Expression e( reUnaryMatch.captured( 1 ).trimmed() ); - opcode = Expression::e_not; + NifExpr e( reUnaryMatch.captured( 1 ).trimmed() ); + opcode = NifExpr::e_not; rhs = QVariant::fromValue( e ); return; } @@ -225,7 +225,7 @@ void Expression::partition( const QString & cond, int offset /*= 0*/ ) lhs.setValue( version2number( cond ) ); } - opcode = Expression::e_nop; + opcode = NifExpr::e_nop; return; } } @@ -233,10 +233,10 @@ void Expression::partition( const QString & cond, int offset /*= 0*/ ) rstartpos = oendpos + 1; rendpos = cond.size() - 1; - Expression lhsexp( cond.mid( lstartpos, lendpos - lstartpos + 1 ).trimmed() ); - Expression rhsexp( cond.mid( rstartpos, rendpos - rstartpos + 1 ).trimmed() ); + NifExpr lhsexp( cond.mid( lstartpos, lendpos - lstartpos + 1 ).trimmed() ); + NifExpr rhsexp( cond.mid( rstartpos, rendpos - rstartpos + 1 ).trimmed() ); - if ( lhsexp.opcode == Expression::e_nop ) { + if ( lhsexp.opcode == NifExpr::e_nop ) { lhs = lhsexp.lhs; } else { lhs = QVariant::fromValue( lhsexp ); @@ -244,63 +244,63 @@ void Expression::partition( const QString & cond, int offset /*= 0*/ ) opcode = operatorFromString( cond.mid( ostartpos, oendpos - ostartpos ) ); - if ( rhsexp.opcode == Expression::e_nop ) { + if ( rhsexp.opcode == NifExpr::e_nop ) { rhs = rhsexp.lhs; } else { rhs = QVariant::fromValue( rhsexp ); } } -QString Expression::toString() const +QString NifExpr::toString() const { QString l = lhs.toString(); QString r = rhs.toString(); - if ( lhs.type() == QVariant::UserType && lhs.canConvert() ) - l = lhs.value().toString(); + if ( lhs.type() == QVariant::UserType && lhs.canConvert() ) + l = lhs.value().toString(); - if ( rhs.type() == QVariant::UserType && rhs.canConvert() ) - r = rhs.value().toString(); + if ( rhs.type() == QVariant::UserType && rhs.canConvert() ) + r = rhs.value().toString(); switch ( opcode ) { - case Expression::e_not: + case NifExpr::e_not: return QString( "!%1" ).arg( r ); - case Expression::e_not_eq: + case NifExpr::e_not_eq: return QString( "(%1 != %2)" ).arg( l, r ); - case Expression::e_eq: + case NifExpr::e_eq: return QString( "(%1 == %2)" ).arg( l, r ); - case Expression::e_gte: + case NifExpr::e_gte: return QString( "(%1 >= %2)" ).arg( l, r ); - case Expression::e_lte: + case NifExpr::e_lte: return QString( "(%1 <= %2)" ).arg( l, r ); - case Expression::e_gt: + case NifExpr::e_gt: return QString( "(%1 > %2)" ).arg( l, r ); - case Expression::e_lt: + case NifExpr::e_lt: return QString( "(%1 < %2)" ).arg( l, r ); - case Expression::e_bit_and: + case NifExpr::e_bit_and: return QString( "(%1 & %2)" ).arg( l, r ); - case Expression::e_bit_or: + case NifExpr::e_bit_or: return QString( "(%1 | %2)" ).arg( l, r ); - case Expression::e_add: + case NifExpr::e_add: return QString( "(%1 + %2)" ).arg( l, r ); - case Expression::e_sub: + case NifExpr::e_sub: return QString( "(%1 - %2)" ).arg( l, r ); - case Expression::e_div: + case NifExpr::e_div: return QString( "(%1 / %2)" ).arg( l, r ); - case Expression::e_mul: + case NifExpr::e_mul: return QString( "(%1 * %2)" ).arg( l, r ); - case Expression::e_bool_and: + case NifExpr::e_bool_and: return QString( "(%1 && %2)" ).arg( l, r ); - case Expression::e_bool_or: + case NifExpr::e_bool_or: return QString( "(%1 || %2)" ).arg( l, r ); - case Expression::e_nop: + case NifExpr::e_nop: return QString( "%1" ).arg( l ); } return QString(); } -void Expression::NormalizeVariants( QVariant & l, QVariant & r ) const +void NifExpr::NormalizeVariants( QVariant & l, QVariant & r ) const { if ( l.isValid() && r.isValid() ) { if ( l.type() != r.type() ) { diff --git a/src/xml/nifexpr.h b/src/xml/nifexpr.h index 2870df957..7e1fab481 100644 --- a/src/xml/nifexpr.h +++ b/src/xml/nifexpr.h @@ -39,9 +39,9 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include -//! @file nifexpr.h Expression +//! @file nifexpr.h NifExpr -class Expression final +class NifExpr final { enum Operator { @@ -53,20 +53,20 @@ class Expression final Operator opcode; public: - explicit Expression() + explicit NifExpr() { - opcode = Expression::e_nop; + opcode = NifExpr::e_nop; } - Expression( const QString & cond, int startpos, int endpos ) + NifExpr( const QString & cond, int startpos, int endpos ) { - opcode = Expression::e_nop; + opcode = NifExpr::e_nop; partition( cond.mid( startpos, endpos - startpos + 1 ) ); } - Expression( const QString & cond ) + NifExpr( const QString & cond ) { - opcode = Expression::e_nop; + opcode = NifExpr::e_nop; partition( cond ); } @@ -81,37 +81,37 @@ class Expression final NormalizeVariants( l, r ); switch ( opcode ) { - case Expression::e_not: + case NifExpr::e_not: return QVariant::fromValue( !r.toBool() ); - case Expression::e_not_eq: + case NifExpr::e_not_eq: return QVariant::fromValue( l != r ); - case Expression::e_eq: + case NifExpr::e_eq: return QVariant::fromValue( l == r ); - case Expression::e_gte: + case NifExpr::e_gte: return QVariant::fromValue( l.toUInt() >= r.toUInt() ); - case Expression::e_lte: + case NifExpr::e_lte: return QVariant::fromValue( l.toUInt() <= r.toUInt() ); - case Expression::e_gt: + case NifExpr::e_gt: return QVariant::fromValue( l.toUInt() > r.toUInt() ); - case Expression::e_lt: + case NifExpr::e_lt: return QVariant::fromValue( l.toUInt() < r.toUInt() ); - case Expression::e_bit_and: + case NifExpr::e_bit_and: return QVariant::fromValue( l.toUInt() & r.toUInt() ); - case Expression::e_bit_or: + case NifExpr::e_bit_or: return QVariant::fromValue( l.toUInt() | r.toUInt() ); - case Expression::e_add: + case NifExpr::e_add: return QVariant::fromValue( l.toUInt() + r.toUInt() ); - case Expression::e_sub: + case NifExpr::e_sub: return QVariant::fromValue( l.toUInt() - r.toUInt() ); - case Expression::e_div: + case NifExpr::e_div: return QVariant::fromValue( l.toUInt() / r.toUInt() ); - case Expression::e_mul: + case NifExpr::e_mul: return QVariant::fromValue( l.toUInt() * r.toUInt() ); - case Expression::e_bool_and: + case NifExpr::e_bool_and: return QVariant::fromValue( l.toBool() && r.toBool() ); - case Expression::e_bool_or: + case NifExpr::e_bool_or: return QVariant::fromValue( l.toBool() || r.toBool() ); - case Expression::e_nop: + case NifExpr::e_nop: return l; } @@ -139,14 +139,14 @@ class Expression final QVariant convertValue( const QVariant & v, const F & convert ) const { if ( v.type() == QVariant::UserType ) { - if ( v.canConvert() ) - return v.value().evaluateValue( convert ); + if ( v.canConvert() ) + return v.value().evaluateValue( convert ); } return convert( v ); } }; -Q_DECLARE_METATYPE( Expression ) +Q_DECLARE_METATYPE( NifExpr ) #endif From 2a17939e5812402432b2d9a9c6d98e843542480a Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sat, 24 Jun 2017 03:20:22 -0400 Subject: [PATCH 044/152] [Reorg] Fix debug compilation from 42437a2 --- src/gl/glmesh.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gl/glmesh.cpp b/src/gl/glmesh.cpp index 39c8b2ad7..cad9a636f 100644 --- a/src/gl/glmesh.cpp +++ b/src/gl/glmesh.cpp @@ -31,6 +31,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "glmesh.h" +#include "nifstream.h" #include "controllers.h" #include "glscene.h" From f5de104bd8ed367ba29ef5ab426343bee010ba66 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sun, 25 Jun 2017 06:52:57 -0400 Subject: [PATCH 045/152] [Reorg] Update file commands for doxygen --- src/data/nifvalue.cpp | 2 +- src/data/nifvalue.h | 2 +- src/io/material.cpp | 3 +++ src/io/material.h | 2 ++ src/io/nifstream.cpp | 2 ++ src/io/nifstream.h | 2 ++ src/model/nifmodel.h | 2 +- src/model/nifproxymodel.cpp | 2 ++ src/model/nifproxymodel.h | 2 ++ src/model/undocommands.cpp | 2 ++ src/model/undocommands.h | 3 +++ src/spells/materialedit.cpp | 2 +- src/ui/settingsdialog.cpp | 2 ++ src/ui/settingsdialog.h | 3 +++ src/ui/settingspane.h | 2 +- 15 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/data/nifvalue.cpp b/src/data/nifvalue.cpp index f8329f39f..3ed889e1d 100644 --- a/src/data/nifvalue.cpp +++ b/src/data/nifvalue.cpp @@ -36,7 +36,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include -//! @file nifvalue.cpp NifValue, NifIStream, NifOStream, NifSStream +//! @file nifvalue.cpp NifValue QHash NifValue::typeMap; QHash NifValue::typeTxt; diff --git a/src/data/nifvalue.h b/src/data/nifvalue.h index 77782832d..50abb5f3c 100644 --- a/src/data/nifvalue.h +++ b/src/data/nifvalue.h @@ -42,7 +42,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include -//! @file nifvalue.h NifValue, NifIStream, NifOStream, NifSStream +//! @file nifvalue.h NifValue // if there is demand for it, consider moving these into Options //! Number of decimals when editing vector types (Vector2, Vector3, Vector4) diff --git a/src/io/material.cpp b/src/io/material.cpp index 383254903..bc2f3c01d 100644 --- a/src/io/material.cpp +++ b/src/io/material.cpp @@ -41,6 +41,9 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include + +//! @file material.cpp BGSM/BGEM file I/O + #define BGSM 0x4D534742 #define BGEM 0x4D454742 diff --git a/src/io/material.h b/src/io/material.h index 799c9b117..eda1e26d7 100644 --- a/src/io/material.h +++ b/src/io/material.h @@ -41,6 +41,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include +//! @file material.h Material, ShaderMaterial, EffectMaterial + class Material : public QObject { Q_OBJECT diff --git a/src/io/nifstream.cpp b/src/io/nifstream.cpp index 2d9a19f44..c498fdcc7 100644 --- a/src/io/nifstream.cpp +++ b/src/io/nifstream.cpp @@ -38,6 +38,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include +//! @file nifstream.cpp NIF file I/O + /* * NifIStream */ diff --git a/src/io/nifstream.h b/src/io/nifstream.h index cba938f4a..c68c158e8 100644 --- a/src/io/nifstream.h +++ b/src/io/nifstream.h @@ -40,6 +40,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include +//! @file nifstream.h NifIStream, NifOStream, NifSStream + class BaseModel; class NifItem; diff --git a/src/model/nifmodel.h b/src/model/nifmodel.h index 3a9ed9b80..283fce59f 100644 --- a/src/model/nifmodel.h +++ b/src/model/nifmodel.h @@ -50,7 +50,7 @@ class SpellBook; using NifBlockPtr = std::shared_ptr; using SpellBookPtr = std::shared_ptr; -//! @file nifmodel.h NifModel, NifModelEval, ChangeValueCommand, ToggleCheckBoxListCommand +//! @file nifmodel.h NifModel, NifModelEval //! The main data model for the NIF file. class NifModel final : public BaseModel diff --git a/src/model/nifproxymodel.cpp b/src/model/nifproxymodel.cpp index 655896660..21f5378ce 100644 --- a/src/model/nifproxymodel.cpp +++ b/src/model/nifproxymodel.cpp @@ -38,6 +38,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include +//! @file nifproxymodel.cpp NifProxyItem + class NifProxyItem { public: diff --git a/src/model/nifproxymodel.h b/src/model/nifproxymodel.h index 686d17fa9..eda1be448 100644 --- a/src/model/nifproxymodel.h +++ b/src/model/nifproxymodel.h @@ -39,6 +39,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include +//! @file nifproxymodel.h NifProxyModel + class NifModel; class NifProxyItem; diff --git a/src/model/undocommands.cpp b/src/model/undocommands.cpp index bcf9d8e46..9426783e1 100644 --- a/src/model/undocommands.cpp +++ b/src/model/undocommands.cpp @@ -35,6 +35,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nifmodel.h" +//! @file undocommands.cpp ChangeValueCommand, ToggleCheckBoxListCommand + /* * ChangeValueCommand */ diff --git a/src/model/undocommands.h b/src/model/undocommands.h index 3b9b7b1b8..767746fbc 100644 --- a/src/model/undocommands.h +++ b/src/model/undocommands.h @@ -39,6 +39,9 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nifvalue.h" + +//! @file undocommands.h ChangeValueCommand, ToggleCheckBoxListCommand + class NifModel; class ChangeValueCommand : public QUndoCommand diff --git a/src/spells/materialedit.cpp b/src/spells/materialedit.cpp index ae874f386..69227d514 100644 --- a/src/spells/materialedit.cpp +++ b/src/spells/materialedit.cpp @@ -4,7 +4,7 @@ // Brief description is deliberately not autolinked to class Spell -/*! \file material.cpp +/*! \file materialedit.cpp * \brief Material editing spells (spMaterialEdit) * * All classes here inherit from the Spell class. diff --git a/src/ui/settingsdialog.cpp b/src/ui/settingsdialog.cpp index a4fabf09b..ab582eb19 100644 --- a/src/ui/settingsdialog.cpp +++ b/src/ui/settingsdialog.cpp @@ -9,6 +9,8 @@ #include +//! @file settingsdialog.cpp SettingsDialog + SettingsDialog::SettingsDialog( QWidget * parent ) : QDialog( parent ), ui( new Ui::SettingsDialog ) diff --git a/src/ui/settingsdialog.h b/src/ui/settingsdialog.h index 52e8c6676..d9fa172dd 100644 --- a/src/ui/settingsdialog.h +++ b/src/ui/settingsdialog.h @@ -6,6 +6,9 @@ #include + +//! @file settingsdialog.h SettingsDialog + class QListWidget; class QListWidgetItem; class QPushButton; diff --git a/src/ui/settingspane.h b/src/ui/settingspane.h index aaeb0a19d..496e92218 100644 --- a/src/ui/settingspane.h +++ b/src/ui/settingspane.h @@ -8,7 +8,7 @@ #include -#define NifSkopeDisplayRole (Qt::UserRole + 42) +//! @file settingspane.h SettingsPane, SettingsGeneral, SettingsRender, SettingsResources class FSManager; From 136aeb20d6b5a92b9c7466b2bec4494383e7dc71 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sun, 25 Jun 2017 11:23:19 -0400 Subject: [PATCH 046/152] [Reorg] Include and forward decl cleanup --- src/data/niftypes.h | 4 ++-- src/gl/bsshape.cpp | 2 ++ src/gl/bsshape.h | 4 +++- src/gl/glmesh.cpp | 2 +- src/gl/glproperty.cpp | 1 + src/gl/glscene.cpp | 9 ++++++++- src/gl/glscene.h | 10 +++++----- src/gl/gltex.cpp | 2 +- src/gl/gltex.h | 6 +----- src/gl/renderer.cpp | 6 ++++-- src/gl/renderer.h | 6 ++++-- src/glview.cpp | 4 +--- src/glview.h | 11 +---------- src/io/nifstream.cpp | 1 + src/io/nifstream.h | 7 ++----- src/lib/importex/3ds.cpp | 4 ++-- src/lib/nvtristripwrapper.cpp | 1 + src/lib/nvtristripwrapper.h | 4 ++-- src/lib/qhull.cpp | 2 ++ src/lib/qhull.h | 5 +++-- src/model/basemodel.cpp | 2 -- src/model/basemodel.h | 1 - src/model/nifmodel.cpp | 2 -- src/model/nifmodel.h | 3 +-- src/model/undocommands.cpp | 14 ++++++++------ src/model/undocommands.h | 3 +-- src/nifskope.h | 7 +------ src/spellbook.h | 3 +-- src/ui/settingspane.h | 8 +++----- src/ui/widgets/floatslider.h | 2 -- src/ui/widgets/nifcheckboxlist.cpp | 3 +++ src/ui/widgets/nifcheckboxlist.h | 9 ++------- src/ui/widgets/nifeditors.h | 5 +++-- src/ui/widgets/refrbrowser.cpp | 2 +- src/ui/widgets/uvedit.cpp | 1 - src/ui/widgets/uvedit.h | 3 ++- src/ui/widgets/valueedit.h | 1 - src/ui/widgets/xmlcheck.cpp | 2 +- src/ui/widgets/xmlcheck.h | 5 +---- src/xml/kfmxml.cpp | 4 ++-- src/xml/nifxml.cpp | 4 ++-- 41 files changed, 79 insertions(+), 96 deletions(-) diff --git a/src/data/niftypes.h b/src/data/niftypes.h index 1e7d3acd5..504ac1e20 100644 --- a/src/data/niftypes.h +++ b/src/data/niftypes.h @@ -33,10 +33,11 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef NIFTYPES_H #define NIFTYPES_H -#include +#include #include #include #include +#include #include #include @@ -55,7 +56,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //! @file niftypes.h Matrix, Matrix4, Triangle, Vector2, Vector3, Vector4, Color3, Color4, Quat class NifModel; -class QDataStream; class QModelIndex; //! Format a float with out of range values diff --git a/src/gl/bsshape.cpp b/src/gl/bsshape.cpp index db0cac98a..860dc84b0 100644 --- a/src/gl/bsshape.cpp +++ b/src/gl/bsshape.cpp @@ -1,6 +1,8 @@ #include "bsshape.h" +#include "renderer.h" #include "glscene.h" +#include "glnode.h" #include "material.h" diff --git a/src/gl/bsshape.h b/src/gl/bsshape.h index 4064ddb3b..9719888e1 100644 --- a/src/gl/bsshape.h +++ b/src/gl/bsshape.h @@ -2,9 +2,11 @@ #define BSSHAPE_H #include "glmesh.h" -#include "glnode.h" #include "gltools.h" + +class NodeList; + class BSShape : public Shape { diff --git a/src/gl/glmesh.cpp b/src/gl/glmesh.cpp index cad9a636f..74c0e9cc4 100644 --- a/src/gl/glmesh.cpp +++ b/src/gl/glmesh.cpp @@ -33,9 +33,9 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "glmesh.h" #include "nifstream.h" +#include "renderer.h" #include "controllers.h" #include "glscene.h" -#include "gltools.h" #include #include diff --git a/src/gl/glproperty.cpp b/src/gl/glproperty.cpp index 22444606e..c9b348b4e 100644 --- a/src/gl/glproperty.cpp +++ b/src/gl/glproperty.cpp @@ -34,6 +34,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "controllers.h" #include "glscene.h" +#include "gltex.h" #include "material.h" #include diff --git a/src/gl/glscene.cpp b/src/gl/glscene.cpp index 315eb8b03..9f39ca58d 100644 --- a/src/gl/glscene.cpp +++ b/src/gl/glscene.cpp @@ -32,10 +32,12 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "glscene.h" +#include "nifmodel.h" +#include "renderer.h" +#include "gltex.h" #include "glcontroller.h" #include "glmesh.h" #include "bsshape.h" -#include "glnode.h" #include "glparticles.h" #include "gltex.h" @@ -98,6 +100,11 @@ Scene::~Scene() delete renderer; } +void Scene::updateShaders() +{ + renderer->updateShaders(); +} + void Scene::clear( bool flushTextures ) { Q_UNUSED( flushTextures ); diff --git a/src/gl/glscene.h b/src/gl/glscene.h index 781ca5472..fdab41dc5 100644 --- a/src/gl/glscene.h +++ b/src/gl/glscene.h @@ -33,13 +33,9 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef GLSCENE_H #define GLSCENE_H -#include "nifmodel.h" - #include "glnode.h" #include "glproperty.h" -#include "gltex.h" #include "gltools.h" -#include "renderer.h" #include #include @@ -51,6 +47,10 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //! @file glscene.h Scene +class NifModel; +class Renderer; +class TexCache; +class Shape; class QOpenGLContext; class QOpenGLFunctions; @@ -61,7 +61,7 @@ class Scene final : public QObject Scene( TexCache * texcache, QOpenGLContext * context, QOpenGLFunctions * functions, QObject * parent = nullptr ); ~Scene(); - void updateShaders() { renderer->updateShaders(); } + void updateShaders(); void clear( bool flushTextures = true ); void make( NifModel * nif, bool flushTextures = false ); diff --git a/src/gl/gltex.cpp b/src/gl/gltex.cpp index 34db20837..364e25a79 100644 --- a/src/gl/gltex.cpp +++ b/src/gl/gltex.cpp @@ -31,9 +31,9 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "gltex.h" - #include "glscene.h" #include "gltexloaders.h" +#include "nifmodel.h" #include #include diff --git a/src/gl/gltex.h b/src/gl/gltex.h index b32d293ed..6f5c42f06 100644 --- a/src/gl/gltex.h +++ b/src/gl/gltex.h @@ -33,8 +33,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef GLTEX_H #define GLTEX_H -#include "niftypes.h" - #include // Inherited #include #include @@ -44,9 +42,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //! @file gltex.h TexCache etc. header -class GroupBox; - -class QAction; +class NifModel; class QFileSystemWatcher; class QOpenGLContext; diff --git a/src/gl/renderer.cpp b/src/gl/renderer.cpp index 4b65af9ad..6c69e56b4 100644 --- a/src/gl/renderer.cpp +++ b/src/gl/renderer.cpp @@ -33,6 +33,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "renderer.h" #include "nifskope.h" +#include "nifmodel.h" + #include "ui/settingsdialog.h" #include "glmesh.h" @@ -41,7 +43,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "gltex.h" #include "material.h" -#include +#include #include #include #include @@ -400,7 +402,7 @@ void Renderer::updateShaders() releaseShaders(); - QDir dir( QApplication::applicationDirPath() ); + QDir dir( QCoreApplication::applicationDirPath() ); if ( dir.exists( "shaders" ) ) dir.cd( "shaders" ); diff --git a/src/gl/renderer.h b/src/gl/renderer.h index f597d6c33..00d126991 100644 --- a/src/gl/renderer.h +++ b/src/gl/renderer.h @@ -33,12 +33,14 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef GLSHADER_H #define GLSHADER_H -#include "nifmodel.h" +#include +#include +#include //! @file renderer.h Renderer, Renderer::ConditionSingle, Renderer::ConditionGroup, Renderer::Shader, Renderer::Program -class Mesh; +class NifModel; class Shape; class PropertyList; diff --git a/src/glview.cpp b/src/glview.cpp index 6a403a70d..a594d3f7f 100644 --- a/src/glview.cpp +++ b/src/glview.cpp @@ -36,12 +36,10 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "ui_nifskope.h" #include "nifskope.h" #include "nifmodel.h" +#include "gl/renderer.h" #include "gl/glmesh.h" -#include "gl/glscene.h" #include "gl/gltex.h" #include "widgets/fileselect.h" -#include "widgets/floatedit.h" -#include "widgets/floatslider.h" #include #include diff --git a/src/glview.h b/src/glview.h index 4ea082bfb..757a06e0e 100644 --- a/src/glview.h +++ b/src/glview.h @@ -33,10 +33,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef GLVIEW #define GLVIEW -#include "nifmodel.h" #include "gl/glscene.h" -#include "widgets/floatedit.h" -#include "widgets/floatslider.h" #include // Inherited #include @@ -49,18 +46,12 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //! @file glview.h GLView, GLGraphicsView class NifSkope; +class NifModel; class GLGraphicsView; -class Scene; -class QAction; -class QActionGroup; -class QComboBox; class QGLFormat; -class QMenu; class QOpenGLContext; class QOpenGLFunctions; -class QSettings; -class QToolBar; class QTimer; diff --git a/src/io/nifstream.cpp b/src/io/nifstream.cpp index c498fdcc7..c97185b81 100644 --- a/src/io/nifstream.cpp +++ b/src/io/nifstream.cpp @@ -31,6 +31,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "nifstream.h" +#include "nifvalue.h" #include "nifmodel.h" #include "half.h" diff --git a/src/io/nifstream.h b/src/io/nifstream.h index c68c158e8..1f6a866a4 100644 --- a/src/io/nifstream.h +++ b/src/io/nifstream.h @@ -33,18 +33,15 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef NIFSTREAM_H #define NIFSTREAM_H -#include "nifvalue.h" - -#include +#include #include //! @file nifstream.h NifIStream, NifOStream, NifSStream +class NifValue; class BaseModel; -class NifItem; - class QDataStream; class QIODevice; diff --git a/src/lib/importex/3ds.cpp b/src/lib/importex/3ds.cpp index 4bbd207d1..eaf63aa21 100644 --- a/src/lib/importex/3ds.cpp +++ b/src/lib/importex/3ds.cpp @@ -11,7 +11,7 @@ #include "spellbook.h" #include "gl/gltex.h" -#include +#include #include #include #include @@ -19,7 +19,7 @@ #include #include -#define tr( x ) QApplication::tr( "3dsImport", x ) +#define tr( x ) QCoreApplication::tr( "3dsImport", x ) struct objPoint diff --git a/src/lib/nvtristripwrapper.cpp b/src/lib/nvtristripwrapper.cpp index 8d183c036..a59696b39 100644 --- a/src/lib/nvtristripwrapper.cpp +++ b/src/lib/nvtristripwrapper.cpp @@ -1,4 +1,5 @@ #include "nvtristripwrapper.h" +#include "niftypes.h" #include diff --git a/src/lib/nvtristripwrapper.h b/src/lib/nvtristripwrapper.h index 79ffe6373..94dc7c2eb 100644 --- a/src/lib/nvtristripwrapper.h +++ b/src/lib/nvtristripwrapper.h @@ -1,12 +1,12 @@ #ifndef NVTRISTRIP_WRAPPER_H #define NVTRISTRIP_WRAPPER_H -#include "niftypes.h" - #include #include +class Triangle; + QList > stripify( QVector triangles, bool stitch = true ); QVector triangulate( QVector strips ); QVector triangulate( QList > strips ); diff --git a/src/lib/qhull.cpp b/src/lib/qhull.cpp index 584e332fb..7c2b98ac3 100644 --- a/src/lib/qhull.cpp +++ b/src/lib/qhull.cpp @@ -32,6 +32,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "qhull.h" +#include "niftypes.h" + #include #include diff --git a/src/lib/qhull.h b/src/lib/qhull.h index 264246f36..4a740f027 100644 --- a/src/lib/qhull.h +++ b/src/lib/qhull.h @@ -33,10 +33,11 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef _QHULL_H #define _QHULL_H -#include "niftypes.h" - #include +class Triangle; +class Vector3; +class Vector4; //! \file qhull.h Header for computing a convex hull diff --git a/src/model/basemodel.cpp b/src/model/basemodel.cpp index 2053b682b..970a4cd86 100644 --- a/src/model/basemodel.cpp +++ b/src/model/basemodel.cpp @@ -32,8 +32,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "basemodel.h" -#include "niftypes.h" - #include #include #include diff --git a/src/model/basemodel.h b/src/model/basemodel.h index 4e25dd225..af6c2bbae 100644 --- a/src/model/basemodel.h +++ b/src/model/basemodel.h @@ -35,7 +35,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "message.h" #include "nifitem.h" -#include "niftypes.h" #include // Inherited #include diff --git a/src/model/nifmodel.cpp b/src/model/nifmodel.cpp index 58e16378b..8bc6f5d63 100644 --- a/src/model/nifmodel.cpp +++ b/src/model/nifmodel.cpp @@ -41,8 +41,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include -#include //! @file nifmodel.cpp The NIF data model. diff --git a/src/model/nifmodel.h b/src/model/nifmodel.h index 283fce59f..02a10be02 100644 --- a/src/model/nifmodel.h +++ b/src/model/nifmodel.h @@ -39,13 +39,12 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include -#include #include class SpellBook; +class QUndoStack; using NifBlockPtr = std::shared_ptr; using SpellBookPtr = std::shared_ptr; diff --git a/src/model/undocommands.cpp b/src/model/undocommands.cpp index 9426783e1..b3a97c6cc 100644 --- a/src/model/undocommands.cpp +++ b/src/model/undocommands.cpp @@ -31,8 +31,10 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "undocommands.h" - #include "nifmodel.h" +#include "nifvalue.h" + +#include //! @file undocommands.cpp ChangeValueCommand, ToggleCheckBoxListCommand @@ -52,9 +54,9 @@ ChangeValueCommand::ChangeValueCommand( const QModelIndex & index, auto newTxt = valueString; if ( !newTxt.isEmpty() ) - setText( QApplication::translate( "ChangeValueCommand", "Set %1 to %2" ).arg( valueType ).arg( newTxt ) ); + setText( QCoreApplication::translate( "ChangeValueCommand", "Set %1 to %2" ).arg( valueType ).arg( newTxt ) ); else - setText( QApplication::translate( "ChangeValueCommand", "Modify %1" ).arg( valueType ) ); + setText( QCoreApplication::translate( "ChangeValueCommand", "Modify %1" ).arg( valueType ) ); } ChangeValueCommand::ChangeValueCommand( const QModelIndex & index, const NifValue & oldVal, @@ -68,9 +70,9 @@ ChangeValueCommand::ChangeValueCommand( const QModelIndex & index, const NifValu auto newTxt = newVal.toString(); if ( !newTxt.isEmpty() ) - setText( QApplication::translate( "ChangeValueCommand", "Set %1 to %2" ).arg( valueType ).arg( newTxt ) ); + setText( QCoreApplication::translate( "ChangeValueCommand", "Set %1 to %2" ).arg( valueType ).arg( newTxt ) ); else - setText( QApplication::translate( "ChangeValueCommand", "Modify %1" ).arg( valueType ) ); + setText( QCoreApplication::translate( "ChangeValueCommand", "Modify %1" ).arg( valueType ) ); } void ChangeValueCommand::redo() @@ -103,7 +105,7 @@ ToggleCheckBoxListCommand::ToggleCheckBoxListCommand( const QModelIndex & index, auto oldTxt = index.data( Qt::DisplayRole ).toString(); - setText( QApplication::translate( "ToggleCheckBoxListCommand", "Modify %1" ).arg( valueType ) ); + setText( QCoreApplication::translate( "ToggleCheckBoxListCommand", "Modify %1" ).arg( valueType ) ); } void ToggleCheckBoxListCommand::redo() diff --git a/src/model/undocommands.h b/src/model/undocommands.h index 767746fbc..dd91d0c78 100644 --- a/src/model/undocommands.h +++ b/src/model/undocommands.h @@ -37,12 +37,11 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -#include "nifvalue.h" - //! @file undocommands.h ChangeValueCommand, ToggleCheckBoxListCommand class NifModel; +class NifValue; class ChangeValueCommand : public QUndoCommand { diff --git a/src/nifskope.h b/src/nifskope.h index 8470b7e5d..d7e98c02c 100644 --- a/src/nifskope.h +++ b/src/nifskope.h @@ -33,11 +33,10 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef NIFSKOPE_H #define NIFSKOPE_H -#include "message.h" - #include // Inherited #include // Inherited #include +#include #include #include @@ -58,7 +57,6 @@ namespace nstypes QString operator"" _uip( const char * str, size_t sz ); } -class FileSelector; class GLView; class GLGraphicsView; class InspectView; @@ -74,13 +72,10 @@ class BSA; class BSAModel; class BSAProxyModel; class QStandardItemModel; - class QAction; class QActionGroup; class QComboBox; class QGraphicsScene; -class QLocale; -class QModelIndex; class QProgressBar; class QStringList; class QTimer; diff --git a/src/spellbook.h b/src/spellbook.h index 9a3fa9588..74433b5f1 100644 --- a/src/spellbook.h +++ b/src/spellbook.h @@ -33,11 +33,10 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef SPELLBOOK_H #define SPELLBOOK_H -#include "message.h" #include "nifmodel.h" #include // Inherited -#include +#include #include #include #include diff --git a/src/ui/settingspane.h b/src/ui/settingspane.h index 496e92218..4a99fcba1 100644 --- a/src/ui/settingspane.h +++ b/src/ui/settingspane.h @@ -1,18 +1,16 @@ #ifndef SETTINGSPANE_H #define SETTINGSPANE_H -#include "ui/settingsdialog.h" -#include "nifskope.h" - #include #include +#include + //! @file settingspane.h SettingsPane, SettingsGeneral, SettingsRender, SettingsResources class FSManager; - -class QListWidgetItem; +class SettingsDialog; class QStringListModel; namespace Ui { diff --git a/src/ui/widgets/floatslider.h b/src/ui/widgets/floatslider.h index d837925ce..8c960316c 100644 --- a/src/ui/widgets/floatslider.h +++ b/src/ui/widgets/floatslider.h @@ -39,8 +39,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //! \file floatslider.h FloatSlider, FloatSliderEditBox, AlphaSlider -class FloatEdit; - //! Frame used by FloatSlider class FloatSliderEditBox final : public QFrame { diff --git a/src/ui/widgets/nifcheckboxlist.cpp b/src/ui/widgets/nifcheckboxlist.cpp index 2f6b59eb4..f8e1ad465 100644 --- a/src/ui/widgets/nifcheckboxlist.cpp +++ b/src/ui/widgets/nifcheckboxlist.cpp @@ -32,14 +32,17 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nifcheckboxlist.h" +#include #include #include +#include #include #include #include #include #include #include +#include CheckBoxList::CheckBoxList( QWidget * widget ) diff --git a/src/ui/widgets/nifcheckboxlist.h b/src/ui/widgets/nifcheckboxlist.h index 0f7dc9105..6c947de07 100644 --- a/src/ui/widgets/nifcheckboxlist.h +++ b/src/ui/widgets/nifcheckboxlist.h @@ -33,18 +33,13 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef NIFCHECKBOXLIST_H #define NIFCHECKBOXLIST_H -#include "nifmodel.h" -#include "nifvalue.h" - #include // Inherited #include // Inherited #include // Inherited #include // Inherited -#include -#include #include -#include -#include + +class QWidget; // Original Implementation by: da-crystal diff --git a/src/ui/widgets/nifeditors.h b/src/ui/widgets/nifeditors.h index 65f3f351b..d63f2eb10 100644 --- a/src/ui/widgets/nifeditors.h +++ b/src/ui/widgets/nifeditors.h @@ -33,15 +33,16 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef NIFEDITORS_H #define NIFEDITORS_H -#include "nifmodel.h" - #include // Inherited #include // Inherited #include +#include #include #include +class NifModel; + class NifEditBox : public QGroupBox { Q_OBJECT diff --git a/src/ui/widgets/refrbrowser.cpp b/src/ui/widgets/refrbrowser.cpp index ce93352df..230a4d193 100644 --- a/src/ui/widgets/refrbrowser.cpp +++ b/src/ui/widgets/refrbrowser.cpp @@ -32,7 +32,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "refrbrowser.h" -#include +#include #include #include "nifmodel.h" diff --git a/src/ui/widgets/uvedit.cpp b/src/ui/widgets/uvedit.cpp index d354dc44b..ddebda129 100644 --- a/src/ui/widgets/uvedit.cpp +++ b/src/ui/widgets/uvedit.cpp @@ -34,7 +34,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nifmodel.h" #include "nifskope.h" -#include "niftypes.h" #include "nvtristripwrapper.h" #include "gl/gltex.h" #include "gl/gltools.h" diff --git a/src/ui/widgets/uvedit.h b/src/ui/widgets/uvedit.h index 606defb94..407af9c12 100644 --- a/src/ui/widgets/uvedit.h +++ b/src/ui/widgets/uvedit.h @@ -33,6 +33,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef UVEDIT_H #define UVEDIT_H +#include "niftypes.h" + #include // Inherited #include // Inherited #include @@ -43,7 +45,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. class NifModel; class TexCache; -class Vector2; class QActionGroup; class QCheckBox; diff --git a/src/ui/widgets/valueedit.h b/src/ui/widgets/valueedit.h index 44c35d9a8..48a6aae91 100644 --- a/src/ui/widgets/valueedit.h +++ b/src/ui/widgets/valueedit.h @@ -48,7 +48,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. class QAction; class QDoubleSpinBox; class QLabel; -class QSpinBox; //! An editing widget for a NifValue class ValueEdit : public QWidget diff --git a/src/ui/widgets/xmlcheck.cpp b/src/ui/widgets/xmlcheck.cpp index 86bd81f1a..70da558da 100644 --- a/src/ui/widgets/xmlcheck.cpp +++ b/src/ui/widgets/xmlcheck.cpp @@ -1,11 +1,11 @@ #include "xmlcheck.h" +#include "message.h" #include "kfmmodel.h" #include "nifmodel.h" #include "fileselect.h" #include -#include #include #include #include diff --git a/src/ui/widgets/xmlcheck.h b/src/ui/widgets/xmlcheck.h index d559a8e64..ff3a96037 100644 --- a/src/ui/widgets/xmlcheck.h +++ b/src/ui/widgets/xmlcheck.h @@ -1,7 +1,6 @@ #ifndef SPELL_DEBUG_H #define SPELL_DEBUG_H -#include "message.h" #include // Inherited #include // Inherited @@ -12,16 +11,14 @@ class QCheckBox; -class QGroupBox; -class QHBoxLayout; class QLabel; class QLineEdit; class QProgressBar; class QPushButton; class QSpinBox; class QTextBrowser; -class QVBoxLayout; +class TestMessage; class FileSelector; class FileQueue final diff --git a/src/xml/kfmxml.cpp b/src/xml/kfmxml.cpp index 8570355d0..bd05ae763 100644 --- a/src/xml/kfmxml.cpp +++ b/src/xml/kfmxml.cpp @@ -34,7 +34,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "kfmmodel.h" #include // QXmlDefaultHandler Inherited -#include +#include #include #define err( X ) { errorStr = X; return false; } @@ -243,7 +243,7 @@ class KfmXmlHandler final : public QXmlDefaultHandler bool KfmModel::loadXML() { - QDir dir( QApplication::applicationDirPath() ); + QDir dir( QCoreApplication::applicationDirPath() ); QString fname; QStringList xmlList( QStringList() << "kfm.xml" diff --git a/src/xml/nifxml.cpp b/src/xml/nifxml.cpp index 4f3458770..a38828179 100644 --- a/src/xml/nifxml.cpp +++ b/src/xml/nifxml.cpp @@ -35,7 +35,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "niftypes.h" #include // QXmlDefaultHandler Inherited -#include +#include #include @@ -592,7 +592,7 @@ class NifXmlHandler final : public QXmlDefaultHandler // documented in nifmodel.h bool NifModel::loadXML() { - QDir dir( QApplication::applicationDirPath() ); + QDir dir( QCoreApplication::applicationDirPath() ); QString fname; QStringList xmlList( QStringList() << "nif.xml" From 531da586ac7313cc7153b5abf6a50deb1556b8e9 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Mon, 26 Jun 2017 11:57:34 -0400 Subject: [PATCH 047/152] [Reorg] More include cleanup Mostly moved message.h and nifmodel.h out of heavily included headers and moving them into the compilation units that need them. (Except spellbook.h still includes nifmodel and message) --- src/gl/bsshape.cpp | 1 + src/gl/bsshape.h | 1 + src/gl/controllers.cpp | 1 + src/gl/controllers.h | 3 +++ src/gl/glcontroller.cpp | 7 ++++++- src/gl/glcontroller.h | 5 +++-- src/gl/glmesh.cpp | 2 ++ src/gl/glmesh.h | 2 ++ src/gl/glnode.cpp | 1 + src/gl/glnode.h | 1 + src/gl/glparticles.cpp | 2 +- src/gl/glproperty.cpp | 2 ++ src/gl/glproperty.h | 2 ++ src/gl/glscene.h | 2 ++ src/gl/gltex.cpp | 1 + src/gl/gltexloaders.cpp | 1 + src/gl/icontrollable.h | 5 ++--- src/gl/renderer.cpp | 1 + src/glview.cpp | 1 + src/lib/importex/col.cpp | 1 + src/lib/importex/obj.cpp | 1 + src/model/basemodel.cpp | 8 ++++++++ src/model/basemodel.h | 4 ++-- src/model/kfmmodel.cpp | 1 + src/model/nifdelegate.cpp | 1 - src/model/nifmodel.cpp | 1 + src/model/nifproxymodel.cpp | 1 + src/nifskope.cpp | 1 + src/nifskope_ui.cpp | 1 + src/spellbook.h | 1 + src/ui/widgets/uvedit.cpp | 1 + 31 files changed, 53 insertions(+), 10 deletions(-) diff --git a/src/gl/bsshape.cpp b/src/gl/bsshape.cpp index 860dc84b0..dc346219c 100644 --- a/src/gl/bsshape.cpp +++ b/src/gl/bsshape.cpp @@ -4,6 +4,7 @@ #include "glscene.h" #include "glnode.h" #include "material.h" +#include "nifmodel.h" BSShape::BSShape( Scene * s, const QModelIndex & b ) : Shape( s, b ) diff --git a/src/gl/bsshape.h b/src/gl/bsshape.h index 9719888e1..6fd2ea1a9 100644 --- a/src/gl/bsshape.h +++ b/src/gl/bsshape.h @@ -5,6 +5,7 @@ #include "gltools.h" +class NifModel; class NodeList; class BSShape : public Shape diff --git a/src/gl/controllers.cpp b/src/gl/controllers.cpp index 2a89528c6..0d0c8de11 100644 --- a/src/gl/controllers.cpp +++ b/src/gl/controllers.cpp @@ -31,6 +31,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "controllers.h" +#include "nifmodel.h" #include "glmesh.h" #include "glnode.h" diff --git a/src/gl/controllers.h b/src/gl/controllers.h index d74367aec..cbe2d4f7e 100644 --- a/src/gl/controllers.h +++ b/src/gl/controllers.h @@ -34,6 +34,9 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define CONTROLLERS_H #include "glcontroller.h" // Inherited +#include "niftypes.h" + +#include //! @file controllers.h Controller subclasses diff --git a/src/gl/glcontroller.cpp b/src/gl/glcontroller.cpp index 55215b36b..5078b4291 100644 --- a/src/gl/glcontroller.cpp +++ b/src/gl/glcontroller.cpp @@ -31,8 +31,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "glcontroller.h" - #include "glscene.h" +#include "nifmodel.h" //! @file glcontroller.cpp Controllable management, Interpolation management @@ -55,6 +55,11 @@ QString IControllable::getName() const return name; } +void IControllable::setController( const NifModel * nif, const QModelIndex & iController ) +{ + Q_UNUSED( nif ); Q_UNUSED( iController ); +} + void IControllable::clear() { name = QString(); diff --git a/src/gl/glcontroller.h b/src/gl/glcontroller.h index 8415f329b..973fe9e46 100644 --- a/src/gl/glcontroller.h +++ b/src/gl/glcontroller.h @@ -33,8 +33,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef GLCONTROLLER_H #define GLCONTROLLER_H -#include "nifmodel.h" - #include // Inherited #include #include @@ -42,6 +40,9 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //! @file glcontroller.h Controller, Interpolator, TransformInterpolator, BSplineTransformInterpolator +class NifModel; +class Transform; + //! Something which can be attached to anything Controllable class Controller { diff --git a/src/gl/glmesh.cpp b/src/gl/glmesh.cpp index 74c0e9cc4..73d4a3241 100644 --- a/src/gl/glmesh.cpp +++ b/src/gl/glmesh.cpp @@ -32,6 +32,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "glmesh.h" #include "nifstream.h" +#include "message.h" +#include "nifmodel.h" #include "renderer.h" #include "controllers.h" diff --git a/src/gl/glmesh.h b/src/gl/glmesh.h index 805041496..44d88d18e 100644 --- a/src/gl/glmesh.h +++ b/src/gl/glmesh.h @@ -43,6 +43,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //! @file glmesh.h Mesh +class NifModel; + class Shape : public Node { friend class MorphController; diff --git a/src/gl/glnode.cpp b/src/gl/glnode.cpp index a71caa121..fbcc0ab9c 100644 --- a/src/gl/glnode.cpp +++ b/src/gl/glnode.cpp @@ -33,6 +33,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "glnode.h" #include "nifskope.h" +#include "nifmodel.h" #include "ui/settingsdialog.h" #include "controllers.h" diff --git a/src/gl/glnode.h b/src/gl/glnode.h index 38b988171..469640c88 100644 --- a/src/gl/glnode.h +++ b/src/gl/glnode.h @@ -44,6 +44,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //! @file glnode.h Node, NodeList class Node; +class NifModel; class NodeList final { diff --git a/src/gl/glparticles.cpp b/src/gl/glparticles.cpp index 26aabe03b..7fafc93a6 100644 --- a/src/gl/glparticles.cpp +++ b/src/gl/glparticles.cpp @@ -31,7 +31,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "glparticles.h" - +#include "nifmodel.h" #include "controllers.h" #include "glscene.h" diff --git a/src/gl/glproperty.cpp b/src/gl/glproperty.cpp index c9b348b4e..387b5a1f2 100644 --- a/src/gl/glproperty.cpp +++ b/src/gl/glproperty.cpp @@ -31,6 +31,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "glproperty.h" +#include "message.h" +#include "nifmodel.h" #include "controllers.h" #include "glscene.h" diff --git a/src/gl/glproperty.h b/src/gl/glproperty.h index 43c8c5661..869a6908e 100644 --- a/src/gl/glproperty.h +++ b/src/gl/glproperty.h @@ -34,6 +34,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define GLPROPERTY_H #include "icontrollable.h" // Inherited +#include "niftypes.h" #include #include @@ -49,6 +50,7 @@ typedef float GLfloat; class Material; +class NifModel; //! Controllable properties attached to nodes and meshes class Property : public IControllable diff --git a/src/gl/glscene.h b/src/gl/glscene.h index fdab41dc5..218cbdb71 100644 --- a/src/gl/glscene.h +++ b/src/gl/glscene.h @@ -37,6 +37,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "glproperty.h" #include "gltools.h" +#include #include #include #include @@ -51,6 +52,7 @@ class NifModel; class Renderer; class TexCache; class Shape; +class QAction; class QOpenGLContext; class QOpenGLFunctions; diff --git a/src/gl/gltex.cpp b/src/gl/gltex.cpp index 364e25a79..f92b5ac51 100644 --- a/src/gl/gltex.cpp +++ b/src/gl/gltex.cpp @@ -34,6 +34,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "glscene.h" #include "gltexloaders.h" #include "nifmodel.h" +#include "message.h" #include #include diff --git a/src/gl/gltexloaders.cpp b/src/gl/gltexloaders.cpp index 4fb84cd35..c837c992e 100644 --- a/src/gl/gltexloaders.cpp +++ b/src/gl/gltexloaders.cpp @@ -33,6 +33,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "gltexloaders.h" #include "nifmodel.h" +#include "message.h" #include "dds/dds_api.h" #include "dds/DirectDrawSurface.h" // unused? check if upstream has cleaner or documented API yet #include "SOIL.h" diff --git a/src/gl/icontrollable.h b/src/gl/icontrollable.h index 9861a071e..8627db517 100644 --- a/src/gl/icontrollable.h +++ b/src/gl/icontrollable.h @@ -33,8 +33,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef ICONTROLLABLE_H #define ICONTROLLABLE_H -#include "nifmodel.h" - #include // Inherited #include #include @@ -45,6 +43,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. class Controller; class Scene; +class NifModel; //! Anything capable of having a Controller class IControllable : public QObject @@ -77,7 +76,7 @@ class IControllable : public QObject protected: //! Sets the Controller - virtual void setController( const NifModel * nif, const QModelIndex & iController ) { Q_UNUSED( nif ); Q_UNUSED( iController ); } + virtual void setController( const NifModel * nif, const QModelIndex & iController ); Scene * scene; diff --git a/src/gl/renderer.cpp b/src/gl/renderer.cpp index 6c69e56b4..40577cdfe 100644 --- a/src/gl/renderer.cpp +++ b/src/gl/renderer.cpp @@ -34,6 +34,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nifskope.h" #include "nifmodel.h" +#include "message.h" #include "ui/settingsdialog.h" diff --git a/src/glview.cpp b/src/glview.cpp index a594d3f7f..ece01d796 100644 --- a/src/glview.cpp +++ b/src/glview.cpp @@ -31,6 +31,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "glview.h" +#include "message.h" #include "ui/settingsdialog.h" #include "ui_nifskope.h" diff --git a/src/lib/importex/col.cpp b/src/lib/importex/col.cpp index 8a312c764..3f7614ab4 100644 --- a/src/lib/importex/col.cpp +++ b/src/lib/importex/col.cpp @@ -31,6 +31,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "nifmodel.h" +#include "message.h" #include "nvtristripwrapper.h" #include "gl/gltex.h" diff --git a/src/lib/importex/obj.cpp b/src/lib/importex/obj.cpp index c7118716c..1f5d7f401 100644 --- a/src/lib/importex/obj.cpp +++ b/src/lib/importex/obj.cpp @@ -31,6 +31,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "nifmodel.h" +#include "message.h" #include "nvtristripwrapper.h" #include "gl/gltex.h" diff --git a/src/model/basemodel.cpp b/src/model/basemodel.cpp index 970a4cd86..191240e74 100644 --- a/src/model/basemodel.cpp +++ b/src/model/basemodel.cpp @@ -31,6 +31,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "basemodel.h" +#include "message.h" #include #include @@ -110,6 +111,13 @@ bool BaseModel::getProcessingResult() return result; } +QList BaseModel::getMessages() const +{ + QList lst = messages; + messages.clear(); + return lst; +} + /* * array functions */ diff --git a/src/model/basemodel.h b/src/model/basemodel.h index af6c2bbae..d19ea9daf 100644 --- a/src/model/basemodel.h +++ b/src/model/basemodel.h @@ -33,7 +33,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef BaseModel_H #define BaseModel_H -#include "message.h" #include "nifitem.h" #include // Inherited @@ -49,6 +48,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //! @file basemodel.h BaseModel, BaseModelEval +class TestMessage; class QAbstractItemDelegate; /*! Base class for NIF and KFM models, which store files in memory. @@ -225,7 +225,7 @@ class BaseModel : public QAbstractItemModel bool getProcessingResult(); //! Get Messages collected - QList getMessages() const { QList lst = messages; messages.clear(); return lst; } + QList getMessages() const; //! Column names enum diff --git a/src/model/kfmmodel.cpp b/src/model/kfmmodel.cpp index d2eb5fcce..4b733985e 100644 --- a/src/model/kfmmodel.cpp +++ b/src/model/kfmmodel.cpp @@ -32,6 +32,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "kfmmodel.h" #include "nifstream.h" +#include "message.h" //! @file kfmmodel.cpp KfmModel diff --git a/src/model/nifdelegate.cpp b/src/model/nifdelegate.cpp index 01c3004a0..7476bff42 100644 --- a/src/model/nifdelegate.cpp +++ b/src/model/nifdelegate.cpp @@ -30,7 +30,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ -#include "glview.h" #include "kfmmodel.h" #include "nifmodel.h" #include "nifproxymodel.h" diff --git a/src/model/nifmodel.cpp b/src/model/nifmodel.cpp index 8bc6f5d63..5040d1527 100644 --- a/src/model/nifmodel.cpp +++ b/src/model/nifmodel.cpp @@ -35,6 +35,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "niftypes.h" #include "spellbook.h" +#include "message.h" #include #include diff --git a/src/model/nifproxymodel.cpp b/src/model/nifproxymodel.cpp index 21f5378ce..d6eaa7627 100644 --- a/src/model/nifproxymodel.cpp +++ b/src/model/nifproxymodel.cpp @@ -33,6 +33,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nifproxymodel.h" #include "nifmodel.h" +#include "message.h" #include #include diff --git a/src/nifskope.cpp b/src/nifskope.cpp index a3e2d8fa8..0dc9f1d41 100644 --- a/src/nifskope.cpp +++ b/src/nifskope.cpp @@ -32,6 +32,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nifskope.h" #include "version.h" +#include "message.h" #include "ui_nifskope.h" #include "ui/about_dialog.h" diff --git a/src/nifskope_ui.cpp b/src/nifskope_ui.cpp index 201a11dfa..97003a848 100644 --- a/src/nifskope_ui.cpp +++ b/src/nifskope_ui.cpp @@ -32,6 +32,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nifskope.h" #include "version.h" +#include "message.h" #include "ui_nifskope.h" #include "ui/about_dialog.h" diff --git a/src/spellbook.h b/src/spellbook.h index 74433b5f1..9c5a1eca5 100644 --- a/src/spellbook.h +++ b/src/spellbook.h @@ -34,6 +34,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define SPELLBOOK_H #include "nifmodel.h" +#include "message.h" #include // Inherited #include diff --git a/src/ui/widgets/uvedit.cpp b/src/ui/widgets/uvedit.cpp index ddebda129..21c115d0a 100644 --- a/src/ui/widgets/uvedit.cpp +++ b/src/ui/widgets/uvedit.cpp @@ -33,6 +33,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "uvedit.h" #include "nifmodel.h" +#include "message.h" #include "nifskope.h" #include "nvtristripwrapper.h" #include "gl/gltex.h" From 6256d5fa672e573852a7c77bfedc04be527b2cdc Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Mon, 26 Jun 2017 13:38:20 -0400 Subject: [PATCH 048/152] [Reorg] Organize include paths Removed include dirs from the project file and updated all include paths to be from the src root save for when a source file includes its own header. --- NifSkope.pro | 2 +- src/data/nifitem.h | 4 ++-- src/data/niftypes.cpp | 2 +- src/data/nifvalue.cpp | 3 ++- src/data/nifvalue.h | 2 +- src/gl/bsshape.cpp | 10 +++++----- src/gl/bsshape.h | 4 ++-- src/gl/controllers.cpp | 12 ++++++------ src/gl/controllers.h | 4 ++-- src/gl/glcontroller.cpp | 5 +++-- src/gl/glmesh.cpp | 12 ++++++------ src/gl/glmesh.h | 4 ++-- src/gl/glnode.cpp | 15 +++++++-------- src/gl/glnode.h | 4 ++-- src/gl/glparticles.cpp | 6 +++--- src/gl/glproperty.cpp | 12 ++++++------ src/gl/glproperty.h | 2 +- src/gl/glscene.cpp | 16 ++++++++-------- src/gl/gltex.cpp | 7 ++++--- src/gl/gltexloaders.cpp | 9 +++++---- src/gl/gltools.cpp | 10 +++++----- src/gl/gltools.h | 2 +- src/gl/renderer.cpp | 16 +++++++--------- src/glview.cpp | 11 ++++++----- src/io/material.h | 2 +- src/io/nifstream.cpp | 8 +++++--- src/lib/importex/3ds.cpp | 3 ++- src/lib/importex/col.cpp | 5 +++-- src/lib/importex/importex.cpp | 6 +++--- src/lib/importex/obj.cpp | 5 +++-- src/lib/nvtristripwrapper.cpp | 2 +- src/lib/qhull.cpp | 2 +- src/main.cpp | 20 +++++++++----------- src/model/basemodel.cpp | 1 + src/model/basemodel.h | 2 +- src/model/kfmmodel.cpp | 3 ++- src/model/nifdelegate.cpp | 12 ++++++------ src/model/nifmodel.cpp | 6 +++--- src/model/nifproxymodel.cpp | 2 +- src/model/undocommands.cpp | 5 +++-- src/nifskope.cpp | 26 ++++++++++++-------------- src/nifskope_ui.cpp | 31 +++++++++++++++---------------- src/spellbook.h | 2 +- src/spells/bounds.cpp | 2 +- src/spells/color.cpp | 2 +- src/spells/havok.cpp | 7 ++++--- src/spells/light.cpp | 2 +- src/spells/materialedit.cpp | 2 +- src/spells/normals.cpp | 2 +- src/spells/optimize.cpp | 8 ++++---- src/spells/sanitize.cpp | 2 +- src/spells/skeleton.cpp | 5 +++-- src/spells/strippify.cpp | 2 +- src/spells/tangentspace.cpp | 2 +- src/spells/texture.cpp | 11 ++++++----- src/spells/transform.cpp | 2 +- src/ui/nifskope.ui | 4 ++-- src/ui/settingsdialog.cpp | 2 +- src/ui/settingspane.cpp | 9 ++++----- src/ui/widgets/colorwheel.cpp | 6 +++--- src/ui/widgets/floatedit.cpp | 3 ++- src/ui/widgets/floatslider.cpp | 5 +++-- src/ui/widgets/inspect.cpp | 4 ++-- src/ui/widgets/nifeditors.cpp | 8 ++++---- src/ui/widgets/nifview.cpp | 6 +++--- src/ui/widgets/nifview.h | 2 +- src/ui/widgets/refrbrowser.cpp | 3 ++- src/ui/widgets/uvedit.cpp | 5 +++-- src/ui/widgets/uvedit.h | 2 +- src/ui/widgets/valueedit.cpp | 2 +- src/ui/widgets/valueedit.h | 2 +- src/ui/widgets/xmlcheck.cpp | 8 ++++---- src/xml/kfmxml.cpp | 2 +- src/xml/nifexpr.cpp | 4 ---- src/xml/nifxml.cpp | 4 ++-- 75 files changed, 227 insertions(+), 220 deletions(-) diff --git a/NifSkope.pro b/NifSkope.pro index 346ff5ca1..c6c8856da 100644 --- a/NifSkope.pro +++ b/NifSkope.pro @@ -135,7 +135,7 @@ include(NifSkope_targets.pri) ## PROJECT SCOPES ############################### -INCLUDEPATH += src lib src/lib src/data src/io src/model src/ui src/xml +INCLUDEPATH += src lib HEADERS += \ src/data/nifitem.h \ diff --git a/src/data/nifitem.h b/src/data/nifitem.h index d2e7068ec..3569d0a22 100644 --- a/src/data/nifitem.h +++ b/src/data/nifitem.h @@ -33,8 +33,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef NIFITEM_H #define NIFITEM_H -#include "nifexpr.h" -#include "nifvalue.h" +#include "data/nifvalue.h" +#include "xml/nifexpr.h" #include // Inherited #include diff --git a/src/data/niftypes.cpp b/src/data/niftypes.cpp index 335d49e08..2d8a76fa4 100644 --- a/src/data/niftypes.cpp +++ b/src/data/niftypes.cpp @@ -32,7 +32,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "niftypes.h" -#include "nifmodel.h" +#include "model/nifmodel.h" #include diff --git a/src/data/nifvalue.cpp b/src/data/nifvalue.cpp index 3ed889e1d..a8d78adbc 100644 --- a/src/data/nifvalue.cpp +++ b/src/data/nifvalue.cpp @@ -31,7 +31,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "nifvalue.h" -#include "nifmodel.h" + +#include "model/nifmodel.h" #include diff --git a/src/data/nifvalue.h b/src/data/nifvalue.h index 50abb5f3c..6408f883a 100644 --- a/src/data/nifvalue.h +++ b/src/data/nifvalue.h @@ -33,7 +33,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef NIFVALUE_H #define NIFVALUE_H -#include "niftypes.h" +#include "data/niftypes.h" #include #include diff --git a/src/gl/bsshape.cpp b/src/gl/bsshape.cpp index dc346219c..21e1f8da9 100644 --- a/src/gl/bsshape.cpp +++ b/src/gl/bsshape.cpp @@ -1,10 +1,10 @@ #include "bsshape.h" -#include "renderer.h" -#include "glscene.h" -#include "glnode.h" -#include "material.h" -#include "nifmodel.h" +#include "gl/glnode.h" +#include "gl/glscene.h" +#include "gl/renderer.h" +#include "io/material.h" +#include "model/nifmodel.h" BSShape::BSShape( Scene * s, const QModelIndex & b ) : Shape( s, b ) diff --git a/src/gl/bsshape.h b/src/gl/bsshape.h index 6fd2ea1a9..1b2f9d19a 100644 --- a/src/gl/bsshape.h +++ b/src/gl/bsshape.h @@ -1,8 +1,8 @@ #ifndef BSSHAPE_H #define BSSHAPE_H -#include "glmesh.h" -#include "gltools.h" +#include "gl/glmesh.h" +#include "gl/gltools.h" class NifModel; diff --git a/src/gl/controllers.cpp b/src/gl/controllers.cpp index 0d0c8de11..59522220b 100644 --- a/src/gl/controllers.cpp +++ b/src/gl/controllers.cpp @@ -31,13 +31,13 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "controllers.h" -#include "nifmodel.h" -#include "glmesh.h" -#include "glnode.h" -#include "glparticles.h" -#include "glproperty.h" -#include "glscene.h" +#include "gl/glmesh.h" +#include "gl/glnode.h" +#include "gl/glparticles.h" +#include "gl/glproperty.h" +#include "gl/glscene.h" +#include "model/nifmodel.h" // `NiControllerManager` blocks diff --git a/src/gl/controllers.h b/src/gl/controllers.h index cbe2d4f7e..30ab83277 100644 --- a/src/gl/controllers.h +++ b/src/gl/controllers.h @@ -33,8 +33,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef CONTROLLERS_H #define CONTROLLERS_H -#include "glcontroller.h" // Inherited -#include "niftypes.h" +#include "gl/glcontroller.h" // Inherited +#include "data/niftypes.h" #include diff --git a/src/gl/glcontroller.cpp b/src/gl/glcontroller.cpp index 5078b4291..f513063da 100644 --- a/src/gl/glcontroller.cpp +++ b/src/gl/glcontroller.cpp @@ -31,8 +31,9 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "glcontroller.h" -#include "glscene.h" -#include "nifmodel.h" + +#include "gl/glscene.h" +#include "model/nifmodel.h" //! @file glcontroller.cpp Controllable management, Interpolation management diff --git a/src/gl/glmesh.cpp b/src/gl/glmesh.cpp index 73d4a3241..58b33980e 100644 --- a/src/gl/glmesh.cpp +++ b/src/gl/glmesh.cpp @@ -31,13 +31,13 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "glmesh.h" -#include "nifstream.h" -#include "message.h" -#include "nifmodel.h" -#include "renderer.h" -#include "controllers.h" -#include "glscene.h" +#include "message.h" +#include "gl/controllers.h" +#include "gl/glscene.h" +#include "gl/renderer.h" +#include "io/nifstream.h" +#include "model/nifmodel.h" #include #include diff --git a/src/gl/glmesh.h b/src/gl/glmesh.h index 44d88d18e..978f6fe29 100644 --- a/src/gl/glmesh.h +++ b/src/gl/glmesh.h @@ -33,8 +33,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef GLMESH_H #define GLMESH_H -#include "glnode.h" // Inherited -#include "gltools.h" +#include "gl/glnode.h" // Inherited +#include "gl/gltools.h" #include #include diff --git a/src/gl/glnode.cpp b/src/gl/glnode.cpp index fbcc0ab9c..990e9d734 100644 --- a/src/gl/glnode.cpp +++ b/src/gl/glnode.cpp @@ -33,16 +33,15 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "glnode.h" #include "nifskope.h" -#include "nifmodel.h" +#include "gl/controllers.h" +#include "gl/glmarker.h" +#include "gl/glscene.h" +#include "gl/marker/furniture.h" +#include "gl/marker/constraints.h" +#include "model/nifmodel.h" #include "ui/settingsdialog.h" -#include "controllers.h" -#include "glmarker.h" -#include "glscene.h" - -#include "marker/furniture.h" -#include "marker/constraints.h" -#include "nvtristripwrapper.h" +#include "lib/nvtristripwrapper.h" #include #include diff --git a/src/gl/glnode.h b/src/gl/glnode.h index 469640c88..e1275b6da 100644 --- a/src/gl/glnode.h +++ b/src/gl/glnode.h @@ -33,8 +33,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef GLNODE_H #define GLNODE_H -#include "icontrollable.h" // Inherited -#include "glproperty.h" +#include "gl/icontrollable.h" // Inherited +#include "gl/glproperty.h" #include #include diff --git a/src/gl/glparticles.cpp b/src/gl/glparticles.cpp index 7fafc93a6..d4441756f 100644 --- a/src/gl/glparticles.cpp +++ b/src/gl/glparticles.cpp @@ -31,10 +31,10 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "glparticles.h" -#include "nifmodel.h" -#include "controllers.h" -#include "glscene.h" +#include "gl/controllers.h" +#include "gl/glscene.h" +#include "model/nifmodel.h" #include diff --git a/src/gl/glproperty.cpp b/src/gl/glproperty.cpp index 387b5a1f2..9619ae897 100644 --- a/src/gl/glproperty.cpp +++ b/src/gl/glproperty.cpp @@ -31,13 +31,13 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "glproperty.h" -#include "message.h" -#include "nifmodel.h" -#include "controllers.h" -#include "glscene.h" -#include "gltex.h" -#include "material.h" +#include "message.h" +#include "gl/controllers.h" +#include "gl/glscene.h" +#include "gl/gltex.h" +#include "io/material.h" +#include "model/nifmodel.h" #include diff --git a/src/gl/glproperty.h b/src/gl/glproperty.h index 869a6908e..19256c692 100644 --- a/src/gl/glproperty.h +++ b/src/gl/glproperty.h @@ -34,7 +34,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define GLPROPERTY_H #include "icontrollable.h" // Inherited -#include "niftypes.h" +#include "data/niftypes.h" #include #include diff --git a/src/gl/glscene.cpp b/src/gl/glscene.cpp index 9f39ca58d..9bd9604bb 100644 --- a/src/gl/glscene.cpp +++ b/src/gl/glscene.cpp @@ -32,14 +32,14 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "glscene.h" -#include "nifmodel.h" -#include "renderer.h" -#include "gltex.h" -#include "glcontroller.h" -#include "glmesh.h" -#include "bsshape.h" -#include "glparticles.h" -#include "gltex.h" +#include "gl/renderer.h" +#include "gl/gltex.h" +#include "gl/glcontroller.h" +#include "gl/glmesh.h" +#include "gl/bsshape.h" +#include "gl/glparticles.h" +#include "gl/gltex.h" +#include "model/nifmodel.h" #include #include diff --git a/src/gl/gltex.cpp b/src/gl/gltex.cpp index f92b5ac51..6a420ddf7 100644 --- a/src/gl/gltex.cpp +++ b/src/gl/gltex.cpp @@ -31,10 +31,11 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "gltex.h" -#include "glscene.h" -#include "gltexloaders.h" -#include "nifmodel.h" + #include "message.h" +#include "gl/glscene.h" +#include "gl/gltexloaders.h" +#include "model/nifmodel.h" #include #include diff --git a/src/gl/gltexloaders.cpp b/src/gl/gltexloaders.cpp index c837c992e..6a0c161c8 100644 --- a/src/gl/gltexloaders.cpp +++ b/src/gl/gltexloaders.cpp @@ -32,11 +32,12 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "gltexloaders.h" -#include "nifmodel.h" #include "message.h" -#include "dds/dds_api.h" -#include "dds/DirectDrawSurface.h" // unused? check if upstream has cleaner or documented API yet -#include "SOIL.h" +#include "model/nifmodel.h" +#include "gl/dds/dds_api.h" +#include "gl/dds/DirectDrawSurface.h" // unused? check if upstream has cleaner or documented API yet + +#include #include #include diff --git a/src/gl/gltools.cpp b/src/gl/gltools.cpp index 8299ce41b..326bf9e8b 100644 --- a/src/gl/gltools.cpp +++ b/src/gl/gltools.cpp @@ -32,16 +32,16 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "gltools.h" -#include -#include -#include -#include +#include "model/nifmodel.h" #include #include #include -#include "nifmodel.h" +#include +#include +#include +#include //! \file gltools.cpp GL helper functions diff --git a/src/gl/gltools.h b/src/gl/gltools.h index 32f4f29dc..887d185e0 100644 --- a/src/gl/gltools.h +++ b/src/gl/gltools.h @@ -33,7 +33,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef GLTOOLS_H #define GLTOOLS_H -#include "niftypes.h" +#include "data/niftypes.h" #include diff --git a/src/gl/renderer.cpp b/src/gl/renderer.cpp index 40577cdfe..f9675026f 100644 --- a/src/gl/renderer.cpp +++ b/src/gl/renderer.cpp @@ -32,18 +32,16 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "renderer.h" -#include "nifskope.h" -#include "nifmodel.h" #include "message.h" - +#include "nifskope.h" +#include "gl/glmesh.h" +#include "gl/glproperty.h" +#include "gl/glscene.h" +#include "gl/gltex.h" +#include "io/material.h" +#include "model/nifmodel.h" #include "ui/settingsdialog.h" -#include "glmesh.h" -#include "glproperty.h" -#include "glscene.h" -#include "gltex.h" -#include "material.h" - #include #include #include diff --git a/src/glview.cpp b/src/glview.cpp index ece01d796..5e3110726 100644 --- a/src/glview.cpp +++ b/src/glview.cpp @@ -31,18 +31,19 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "glview.h" -#include "message.h" -#include "ui/settingsdialog.h" -#include "ui_nifskope.h" +#include "message.h" #include "nifskope.h" -#include "nifmodel.h" #include "gl/renderer.h" #include "gl/glmesh.h" #include "gl/gltex.h" -#include "widgets/fileselect.h" +#include "model/nifmodel.h" +#include "ui/settingsdialog.h" +#include "ui/widgets/fileselect.h" +#include #include +#include #include #include #include diff --git a/src/io/material.h b/src/io/material.h index eda1e26d7..df4465b1b 100644 --- a/src/io/material.h +++ b/src/io/material.h @@ -33,7 +33,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef MATERIAL_H #define MATERIAL_H -#include "niftypes.h" +#include "data/niftypes.h" #include #include diff --git a/src/io/nifstream.cpp b/src/io/nifstream.cpp index c97185b81..614511165 100644 --- a/src/io/nifstream.cpp +++ b/src/io/nifstream.cpp @@ -31,9 +31,11 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "nifstream.h" -#include "nifvalue.h" -#include "nifmodel.h" -#include "half.h" + +#include "data/nifvalue.h" +#include "model/nifmodel.h" + +#include "lib/half.h" #include #include diff --git a/src/lib/importex/3ds.cpp b/src/lib/importex/3ds.cpp index eaf63aa21..568f6e87e 100644 --- a/src/lib/importex/3ds.cpp +++ b/src/lib/importex/3ds.cpp @@ -7,10 +7,11 @@ #include "3ds.h" -#include "nvtristripwrapper.h" #include "spellbook.h" #include "gl/gltex.h" +#include "lib/nvtristripwrapper.h" + #include #include #include diff --git a/src/lib/importex/col.cpp b/src/lib/importex/col.cpp index 3f7614ab4..ee670e6ce 100644 --- a/src/lib/importex/col.cpp +++ b/src/lib/importex/col.cpp @@ -30,10 +30,11 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ -#include "nifmodel.h" #include "message.h" -#include "nvtristripwrapper.h" #include "gl/gltex.h" +#include "model/nifmodel.h" + +#include "lib/nvtristripwrapper.h" #include #include diff --git a/src/lib/importex/importex.cpp b/src/lib/importex/importex.cpp index d43bb3b94..c33e9e296 100644 --- a/src/lib/importex/importex.cpp +++ b/src/lib/importex/importex.cpp @@ -30,10 +30,10 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ -#include "nifmodel.h" -#include "nifproxymodel.h" #include "nifskope.h" -#include "widgets/nifview.h" +#include "model/nifmodel.h" +#include "model/nifproxymodel.h" +#include "ui/widgets/nifview.h" #include #include diff --git a/src/lib/importex/obj.cpp b/src/lib/importex/obj.cpp index 1f5d7f401..a0cbad69f 100644 --- a/src/lib/importex/obj.cpp +++ b/src/lib/importex/obj.cpp @@ -30,10 +30,11 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ -#include "nifmodel.h" #include "message.h" -#include "nvtristripwrapper.h" #include "gl/gltex.h" +#include "model/nifmodel.h" + +#include "lib/nvtristripwrapper.h" #include #include diff --git a/src/lib/nvtristripwrapper.cpp b/src/lib/nvtristripwrapper.cpp index a59696b39..5941fb574 100644 --- a/src/lib/nvtristripwrapper.cpp +++ b/src/lib/nvtristripwrapper.cpp @@ -1,5 +1,5 @@ #include "nvtristripwrapper.h" -#include "niftypes.h" +#include "data/niftypes.h" #include diff --git a/src/lib/qhull.cpp b/src/lib/qhull.cpp index 7c2b98ac3..e2a2a2236 100644 --- a/src/lib/qhull.cpp +++ b/src/lib/qhull.cpp @@ -32,7 +32,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "qhull.h" -#include "niftypes.h" +#include "data/niftypes.h" #include #include diff --git a/src/main.cpp b/src/main.cpp index 3872c3beb..274acc2fa 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -30,23 +30,21 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ +#include "nifskope.h" +#include "version.h" +#include "data/nifvalue.h" +#include "model/nifmodel.h" +#include "model/kfmmodel.h" + #include -#include -#include -#include -#include #include - #include +#include +#include +#include #include #include -#include "nifskope.h" -#include "nifvalue.h" -#include "nifmodel.h" -#include "kfmmodel.h" -#include "version.h" - QCoreApplication * createApplication( int &argc, char *argv[] ) { diff --git a/src/model/basemodel.cpp b/src/model/basemodel.cpp index 191240e74..adc1d0a55 100644 --- a/src/model/basemodel.cpp +++ b/src/model/basemodel.cpp @@ -31,6 +31,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "basemodel.h" + #include "message.h" #include diff --git a/src/model/basemodel.h b/src/model/basemodel.h index d19ea9daf..de8199113 100644 --- a/src/model/basemodel.h +++ b/src/model/basemodel.h @@ -33,7 +33,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef BaseModel_H #define BaseModel_H -#include "nifitem.h" +#include "data/nifitem.h" #include // Inherited #include diff --git a/src/model/kfmmodel.cpp b/src/model/kfmmodel.cpp index 4b733985e..6140af34c 100644 --- a/src/model/kfmmodel.cpp +++ b/src/model/kfmmodel.cpp @@ -31,8 +31,9 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "kfmmodel.h" -#include "nifstream.h" + #include "message.h" +#include "io/nifstream.h" //! @file kfmmodel.cpp KfmModel diff --git a/src/model/nifdelegate.cpp b/src/model/nifdelegate.cpp index 7476bff42..e70ab0d24 100644 --- a/src/model/nifdelegate.cpp +++ b/src/model/nifdelegate.cpp @@ -30,13 +30,13 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ -#include "kfmmodel.h" -#include "nifmodel.h" -#include "nifproxymodel.h" #include "spellbook.h" -#include "undocommands.h" -#include "widgets/valueedit.h" -#include "widgets/nifcheckboxlist.h" +#include "model/kfmmodel.h" +#include "model/nifmodel.h" +#include "model/nifproxymodel.h" +#include "model/undocommands.h" +#include "ui/widgets/valueedit.h" +#include "ui/widgets/nifcheckboxlist.h" #include // Inherited #include diff --git a/src/model/nifmodel.cpp b/src/model/nifmodel.cpp index 5040d1527..c60964779 100644 --- a/src/model/nifmodel.cpp +++ b/src/model/nifmodel.cpp @@ -31,11 +31,11 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "nifmodel.h" -#include "nifstream.h" -#include "niftypes.h" -#include "spellbook.h" #include "message.h" +#include "spellbook.h" +#include "data/niftypes.h" +#include "io/nifstream.h" #include #include diff --git a/src/model/nifproxymodel.cpp b/src/model/nifproxymodel.cpp index d6eaa7627..1898f65cb 100644 --- a/src/model/nifproxymodel.cpp +++ b/src/model/nifproxymodel.cpp @@ -32,8 +32,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nifproxymodel.h" -#include "nifmodel.h" #include "message.h" +#include "model/nifmodel.h" #include #include diff --git a/src/model/undocommands.cpp b/src/model/undocommands.cpp index b3a97c6cc..9944730a4 100644 --- a/src/model/undocommands.cpp +++ b/src/model/undocommands.cpp @@ -31,8 +31,9 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "undocommands.h" -#include "nifmodel.h" -#include "nifvalue.h" + +#include "data/nifvalue.h" +#include "model/nifmodel.h" #include diff --git a/src/nifskope.cpp b/src/nifskope.cpp index 0dc9f1d41..a5832c140 100644 --- a/src/nifskope.cpp +++ b/src/nifskope.cpp @@ -31,24 +31,22 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "nifskope.h" -#include "version.h" -#include "message.h" - #include "ui_nifskope.h" -#include "ui/about_dialog.h" -#include "ui/settingsdialog.h" #include "glview.h" -#include "gl/glscene.h" -#include "kfmmodel.h" -#include "nifmodel.h" -#include "nifproxymodel.h" +#include "message.h" #include "spellbook.h" -#include "widgets/fileselect.h" -#include "widgets/nifview.h" -#include "widgets/refrbrowser.h" -#include "widgets/inspect.h" - +#include "version.h" +#include "gl/glscene.h" +#include "model/kfmmodel.h" +#include "model/nifmodel.h" +#include "model/nifproxymodel.h" +#include "ui/widgets/fileselect.h" +#include "ui/widgets/nifview.h" +#include "ui/widgets/refrbrowser.h" +#include "ui/widgets/inspect.h" +#include "ui/about_dialog.h" +#include "ui/settingsdialog.h" #include #include diff --git a/src/nifskope_ui.cpp b/src/nifskope_ui.cpp index 97003a848..9ac4b56d8 100644 --- a/src/nifskope_ui.cpp +++ b/src/nifskope_ui.cpp @@ -31,26 +31,25 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "nifskope.h" -#include "version.h" -#include "message.h" - #include "ui_nifskope.h" -#include "ui/about_dialog.h" -#include "ui/settingsdialog.h" #include "glview.h" -#include "gl/glscene.h" -#include "kfmmodel.h" -#include "nifmodel.h" -#include "nifproxymodel.h" +#include "message.h" #include "spellbook.h" -#include "widgets/fileselect.h" -#include "widgets/floatslider.h" -#include "widgets/floatedit.h" -#include "widgets/nifview.h" -#include "widgets/refrbrowser.h" -#include "widgets/inspect.h" -#include "widgets/xmlcheck.h" +#include "version.h" +#include "gl/glscene.h" +#include "model/kfmmodel.h" +#include "model/nifmodel.h" +#include "model/nifproxymodel.h" +#include "ui/widgets/fileselect.h" +#include "ui/widgets/floatslider.h" +#include "ui/widgets/floatedit.h" +#include "ui/widgets/nifview.h" +#include "ui/widgets/refrbrowser.h" +#include "ui/widgets/inspect.h" +#include "ui/widgets/xmlcheck.h" +#include "ui/about_dialog.h" +#include "ui/settingsdialog.h" #include #include diff --git a/src/spellbook.h b/src/spellbook.h index 9c5a1eca5..84d888db8 100644 --- a/src/spellbook.h +++ b/src/spellbook.h @@ -33,8 +33,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef SPELLBOOK_H #define SPELLBOOK_H -#include "nifmodel.h" #include "message.h" +#include "model/nifmodel.h" #include // Inherited #include diff --git a/src/spells/bounds.cpp b/src/spells/bounds.cpp index be4d9ab9e..53bd7c87d 100644 --- a/src/spells/bounds.cpp +++ b/src/spells/bounds.cpp @@ -1,6 +1,6 @@ #include "spellbook.h" -#include "widgets/nifeditors.h" +#include "ui/widgets/nifeditors.h" // Brief description is deliberately not autolinked to class Spell diff --git a/src/spells/color.cpp b/src/spells/color.cpp index c77d32fc5..b23d731b4 100644 --- a/src/spells/color.cpp +++ b/src/spells/color.cpp @@ -1,6 +1,6 @@ #include "spellbook.h" -#include "widgets/colorwheel.h" +#include "ui/widgets/colorwheel.h" // Brief description is deliberately not autolinked to class Spell diff --git a/src/spells/havok.cpp b/src/spells/havok.cpp index ccb64f6d2..c9d8bc5d0 100644 --- a/src/spells/havok.cpp +++ b/src/spells/havok.cpp @@ -1,8 +1,9 @@ #include "spellbook.h" -#include "blocks.h" -#include "nvtristripwrapper.h" -#include "qhull.h" +#include "spells/blocks.h" + +#include "lib/nvtristripwrapper.h" +#include "lib/qhull.h" #include #include diff --git a/src/spells/light.cpp b/src/spells/light.cpp index 9dc0b7a99..1b2028e3f 100644 --- a/src/spells/light.cpp +++ b/src/spells/light.cpp @@ -1,6 +1,6 @@ #include "spellbook.h" -#include "widgets/nifeditors.h" +#include "ui/widgets/nifeditors.h" // Brief description is deliberately not autolinked to class Spell diff --git a/src/spells/materialedit.cpp b/src/spells/materialedit.cpp index 69227d514..5a0cf00c2 100644 --- a/src/spells/materialedit.cpp +++ b/src/spells/materialedit.cpp @@ -1,6 +1,6 @@ #include "spellbook.h" -#include "widgets/nifeditors.h" +#include "ui/widgets/nifeditors.h" // Brief description is deliberately not autolinked to class Spell diff --git a/src/spells/normals.cpp b/src/spells/normals.cpp index 02a49f8f5..5ce836e29 100644 --- a/src/spells/normals.cpp +++ b/src/spells/normals.cpp @@ -1,6 +1,6 @@ #include "spellbook.h" -#include "nvtristripwrapper.h" +#include "lib/nvtristripwrapper.h" #include #include diff --git a/src/spells/optimize.cpp b/src/spells/optimize.cpp index a31e7a91e..51dcf2d56 100644 --- a/src/spells/optimize.cpp +++ b/src/spells/optimize.cpp @@ -1,9 +1,9 @@ #include "spellbook.h" -#include "blocks.h" -#include "mesh.h" -#include "tangentspace.h" -#include "transform.h" +#include "spells/blocks.h" +#include "spells/mesh.h" +#include "spells/tangentspace.h" +#include "spells/transform.h" #include #include diff --git a/src/spells/sanitize.cpp b/src/spells/sanitize.cpp index a2040c4a6..defad84d6 100644 --- a/src/spells/sanitize.cpp +++ b/src/spells/sanitize.cpp @@ -1,5 +1,5 @@ #include "spellbook.h" -#include "misc.h" +#include "spells/misc.h" #include diff --git a/src/spells/skeleton.cpp b/src/spells/skeleton.cpp index 56d2a247f..3d80c4065 100644 --- a/src/spells/skeleton.cpp +++ b/src/spells/skeleton.cpp @@ -1,9 +1,10 @@ #include "skeleton.h" -#include "spellbook.h" -#include "nvtristripwrapper.h" +#include "spellbook.h" #include "gl/gltools.h" +#include "lib/nvtristripwrapper.h" + #include #include #include diff --git a/src/spells/strippify.cpp b/src/spells/strippify.cpp index bc7c0b5bf..4b94d35aa 100644 --- a/src/spells/strippify.cpp +++ b/src/spells/strippify.cpp @@ -1,6 +1,6 @@ #include "spellbook.h" -#include "nvtristripwrapper.h" +#include "lib/nvtristripwrapper.h" // TODO: Move these to blocks.h / misc.h / wherever diff --git a/src/spells/tangentspace.cpp b/src/spells/tangentspace.cpp index efcaa7789..fad1e0ca3 100644 --- a/src/spells/tangentspace.cpp +++ b/src/spells/tangentspace.cpp @@ -1,6 +1,6 @@ #include "tangentspace.h" -#include "nvtristripwrapper.h" +#include "lib/nvtristripwrapper.h" bool spTangentSpace::isApplicable( const NifModel * nif, const QModelIndex & index ) diff --git a/src/spells/texture.cpp b/src/spells/texture.cpp index e841e7f98..5505743cf 100644 --- a/src/spells/texture.cpp +++ b/src/spells/texture.cpp @@ -1,12 +1,13 @@ #include "texture.h" -#include "blocks.h" -#include "nvtristripwrapper.h" #include "spellbook.h" #include "gl/gltex.h" -#include "widgets/fileselect.h" -#include "widgets/nifeditors.h" -#include "widgets/uvedit.h" +#include "spells/blocks.h" +#include "ui/widgets/fileselect.h" +#include "ui/widgets/nifeditors.h" +#include "ui/widgets/uvedit.h" + +#include "lib/nvtristripwrapper.h" #include #include diff --git a/src/spells/transform.cpp b/src/spells/transform.cpp index bf089a383..ce58724f7 100644 --- a/src/spells/transform.cpp +++ b/src/spells/transform.cpp @@ -1,6 +1,6 @@ #include "transform.h" -#include "widgets/nifeditors.h" +#include "ui/widgets/nifeditors.h" #include #include diff --git a/src/ui/nifskope.ui b/src/ui/nifskope.ui index 490350fe0..d3a2eae3d 100644 --- a/src/ui/nifskope.ui +++ b/src/ui/nifskope.ui @@ -2049,12 +2049,12 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 NifTreeView QTreeView -

widgets/nifview.h
+
ui/widgets/nifview.h
ReferenceBrowser QTextBrowser -
widgets/refrbrowser.h
+
ui/widgets/refrbrowser.h
diff --git a/src/ui/settingsdialog.cpp b/src/ui/settingsdialog.cpp index ab582eb19..bc9278b6c 100644 --- a/src/ui/settingsdialog.cpp +++ b/src/ui/settingsdialog.cpp @@ -1,7 +1,7 @@ #include "settingsdialog.h" #include "ui_settingsdialog.h" -#include "settingspane.h" +#include "ui/settingspane.h" #include #include diff --git a/src/ui/settingspane.cpp b/src/ui/settingspane.cpp index b155f3e5c..118be0f07 100644 --- a/src/ui/settingspane.cpp +++ b/src/ui/settingspane.cpp @@ -1,13 +1,12 @@ #include "settingspane.h" - -#include "widgets/colorwheel.h" -#include "widgets/floatslider.h" -#include "ui/settingsdialog.h" - #include "ui_settingsgeneral.h" #include "ui_settingsrender.h" #include "ui_settingsresources.h" +#include "ui/widgets/colorwheel.h" +#include "ui/widgets/floatslider.h" +#include "ui/settingsdialog.h" + #include #include diff --git a/src/ui/widgets/colorwheel.cpp b/src/ui/widgets/colorwheel.cpp index 87cfd904d..9f4cef2b6 100644 --- a/src/ui/widgets/colorwheel.cpp +++ b/src/ui/widgets/colorwheel.cpp @@ -31,10 +31,10 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "colorwheel.h" -#include "spellbook.h" -#include "floatslider.h" -#include "niftypes.h" +#include "spellbook.h" +#include "data/niftypes.h" +#include "ui/widgets/floatslider.h" #include #include diff --git a/src/ui/widgets/floatedit.cpp b/src/ui/widgets/floatedit.cpp index fd99c9dcc..bd16833e6 100644 --- a/src/ui/widgets/floatedit.cpp +++ b/src/ui/widgets/floatedit.cpp @@ -31,7 +31,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "floatedit.h" -#include "nifvalue.h" + +#include "data/nifvalue.h" #include #include diff --git a/src/ui/widgets/floatslider.cpp b/src/ui/widgets/floatslider.cpp index f19d49f93..9a0b19b6a 100644 --- a/src/ui/widgets/floatslider.cpp +++ b/src/ui/widgets/floatslider.cpp @@ -32,6 +32,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "floatslider.h" +#include "ui/widgets/floatedit.h" + #include #include #include @@ -40,10 +42,9 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -#include "floatedit.h" - #include + //! \file floatslider.cpp FloatSlider et. al. implementation #define VAL_HEIGHT 12 diff --git a/src/ui/widgets/inspect.cpp b/src/ui/widgets/inspect.cpp index 9dcd60acd..dfd655d0c 100644 --- a/src/ui/widgets/inspect.cpp +++ b/src/ui/widgets/inspect.cpp @@ -32,9 +32,9 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "inspect.h" -#include "nifmodel.h" -#include "gl/glscene.h" #include "gl/glnode.h" +#include "gl/glscene.h" +#include "model/nifmodel.h" #include #include diff --git a/src/ui/widgets/nifeditors.cpp b/src/ui/widgets/nifeditors.cpp index ba584a788..06fd5cfd6 100644 --- a/src/ui/widgets/nifeditors.cpp +++ b/src/ui/widgets/nifeditors.cpp @@ -32,10 +32,10 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nifeditors.h" -#include "nifmodel.h" -#include "colorwheel.h" -#include "floatslider.h" -#include "valueedit.h" +#include "model/nifmodel.h" +#include "ui/widgets/colorwheel.h" +#include "ui/widgets/floatslider.h" +#include "ui/widgets/valueedit.h" #include #include diff --git a/src/ui/widgets/nifview.cpp b/src/ui/widgets/nifview.cpp index 985f82dc4..58d0afd13 100644 --- a/src/ui/widgets/nifview.cpp +++ b/src/ui/widgets/nifview.cpp @@ -32,10 +32,10 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nifview.h" -#include "basemodel.h" -#include "nifproxymodel.h" #include "spellbook.h" -#include "undocommands.h" +#include "model/basemodel.h" +#include "model/nifproxymodel.h" +#include "model/undocommands.h" #include #include diff --git a/src/ui/widgets/nifview.h b/src/ui/widgets/nifview.h index b1d295828..b68f045f4 100644 --- a/src/ui/widgets/nifview.h +++ b/src/ui/widgets/nifview.h @@ -34,7 +34,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define NIFTREEVIEW_H #include // Inherited -#include +#include #include diff --git a/src/ui/widgets/refrbrowser.cpp b/src/ui/widgets/refrbrowser.cpp index 230a4d193..ab7b2f4c3 100644 --- a/src/ui/widgets/refrbrowser.cpp +++ b/src/ui/widgets/refrbrowser.cpp @@ -32,10 +32,11 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "refrbrowser.h" +#include "model/nifmodel.h" + #include #include -#include "nifmodel.h" ReferenceBrowser::ReferenceBrowser( QWidget * parent ) : QTextBrowser( parent ) diff --git a/src/ui/widgets/uvedit.cpp b/src/ui/widgets/uvedit.cpp index 21c115d0a..4fb3ef2cc 100644 --- a/src/ui/widgets/uvedit.cpp +++ b/src/ui/widgets/uvedit.cpp @@ -32,14 +32,15 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "uvedit.h" -#include "nifmodel.h" #include "message.h" #include "nifskope.h" -#include "nvtristripwrapper.h" #include "gl/gltex.h" #include "gl/gltools.h" +#include "model/nifmodel.h" #include "ui/settingsdialog.h" +#include "lib/nvtristripwrapper.h" + #include // QUndoCommand Inherited #include #include diff --git a/src/ui/widgets/uvedit.h b/src/ui/widgets/uvedit.h index 407af9c12..b7e31e926 100644 --- a/src/ui/widgets/uvedit.h +++ b/src/ui/widgets/uvedit.h @@ -33,7 +33,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef UVEDIT_H #define UVEDIT_H -#include "niftypes.h" +#include "data/niftypes.h" #include // Inherited #include // Inherited diff --git a/src/ui/widgets/valueedit.cpp b/src/ui/widgets/valueedit.cpp index 3a9c0e691..2912be328 100644 --- a/src/ui/widgets/valueedit.cpp +++ b/src/ui/widgets/valueedit.cpp @@ -32,7 +32,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "valueedit.h" -#include "floatedit.h" +#include "ui/widgets/floatedit.h" #include // Inherited #include // Inherited diff --git a/src/ui/widgets/valueedit.h b/src/ui/widgets/valueedit.h index 48a6aae91..d565d94f9 100644 --- a/src/ui/widgets/valueedit.h +++ b/src/ui/widgets/valueedit.h @@ -33,7 +33,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef VALUEEDIT_H #define VALUEEDIT_H -#include "nifvalue.h" +#include "data/nifvalue.h" #include // Inherited #include // Inherited diff --git a/src/ui/widgets/xmlcheck.cpp b/src/ui/widgets/xmlcheck.cpp index 70da558da..aaf4d54f1 100644 --- a/src/ui/widgets/xmlcheck.cpp +++ b/src/ui/widgets/xmlcheck.cpp @@ -1,9 +1,9 @@ #include "xmlcheck.h" -#include "message.h" -#include "kfmmodel.h" -#include "nifmodel.h" -#include "fileselect.h" +#include "message.h" +#include "model/kfmmodel.h" +#include "model/nifmodel.h" +#include "ui/widgets/fileselect.h" #include #include diff --git a/src/xml/kfmxml.cpp b/src/xml/kfmxml.cpp index bd05ae763..388c63f3b 100644 --- a/src/xml/kfmxml.cpp +++ b/src/xml/kfmxml.cpp @@ -31,7 +31,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "message.h" -#include "kfmmodel.h" +#include "model/kfmmodel.h" #include // QXmlDefaultHandler Inherited #include diff --git a/src/xml/nifexpr.cpp b/src/xml/nifexpr.cpp index 70108aec7..a0bcec61b 100644 --- a/src/xml/nifexpr.cpp +++ b/src/xml/nifexpr.cpp @@ -32,10 +32,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nifexpr.h" -#include - -//#include "basemodel.h" - //! @file nifexpr.cpp Expression parsing for conditions defined in nif.xml. diff --git a/src/xml/nifxml.cpp b/src/xml/nifxml.cpp index a38828179..3cf911213 100644 --- a/src/xml/nifxml.cpp +++ b/src/xml/nifxml.cpp @@ -31,8 +31,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "message.h" -#include "nifmodel.h" -#include "niftypes.h" +#include "data/niftypes.h" +#include "model/nifmodel.h" #include // QXmlDefaultHandler Inherited #include From 450f23bac25efa26a96835985f81e61e0138de1a Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Mon, 26 Jun 2017 13:41:39 -0400 Subject: [PATCH 049/152] [GL] Fix crash with < 4 bones --- src/gl/bsshape.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/gl/bsshape.cpp b/src/gl/bsshape.cpp index 21e1f8da9..a0a4517c8 100644 --- a/src/gl/bsshape.cpp +++ b/src/gl/bsshape.cpp @@ -242,6 +242,9 @@ void BSShape::transform() continue; for ( int j = 0; j < 4; j++ ) { + if ( bns[j] >= weights.count() ) + continue; + if ( wts[j] > 0.0 ) weights[bns[j]].weights << VertexWeight( i, wts[j] ); } From 6da8175a8eb388acf08eaf32aa2e8e33a5ef93a4 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 27 Jun 2017 14:30:30 -0400 Subject: [PATCH 050/152] [Build] Fix game detection for 64-bit builds When built in 64-bit Qt no longer redirects to the WOW6432Node. It needs to be set to Registry32Format to do so. --- lib/fsengine/fsmanager.cpp | 2 +- src/ui/settingspane.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/fsengine/fsmanager.cpp b/lib/fsengine/fsmanager.cpp index caec92afe..f3e1b1143 100644 --- a/lib/fsengine/fsmanager.cpp +++ b/lib/fsengine/fsmanager.cpp @@ -100,7 +100,7 @@ void FSManager::initialize() QStringList FSManager::regPathBSAList( QString regKey, QString dataDir ) { QStringList list; - QSettings reg( regKey, QSettings::NativeFormat ); + QSettings reg( regKey, QSettings::Registry32Format ); QString dataPath = reg.value( "Installed Path" ).toString(); if ( ! dataPath.isEmpty() ) { diff --git a/src/ui/settingspane.cpp b/src/ui/settingspane.cpp index 118be0f07..7076ecf87 100644 --- a/src/ui/settingspane.cpp +++ b/src/ui/settingspane.cpp @@ -444,7 +444,7 @@ void SettingsRender::setDefault() bool regFolderPath( QStringList & gamePaths, const QString & regPath, const QString & regValue, const QString & gameFolder, QStringList gameSubDirs = QStringList(), QStringList gameArchiveFilters = QStringList() ) { - QSettings reg( regPath, QSettings::NativeFormat ); + QSettings reg( regPath, QSettings::Registry32Format ); QDir dir( reg.value( regValue ).toString() ); if ( dir.exists() && dir.cd( gameFolder ) ) { From 157db708b33791ea3a0bd72de929c70808238a7f Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Mon, 3 Jul 2017 20:52:22 -0400 Subject: [PATCH 051/152] [UI] Update URLs --- src/ui/about_dialog.cpp | 4 ++-- src/ui/nifskope.ui | 13 ++++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/ui/about_dialog.cpp b/src/ui/about_dialog.cpp index 5cd2ccff7..3b2a99017 100644 --- a/src/ui/about_dialog.cpp +++ b/src/ui/about_dialog.cpp @@ -15,9 +15,9 @@ AboutDialog::AboutDialog( QWidget * parent ) -

For more information visit our forum.
+

For more information visit our forum.
To receive support for NifSkope please use the - NifSkope Help subforum.

+ NifSkope Help subforum.

The most recent stable version of NifSkope can be downloaded from the official GitHub release page.

diff --git a/src/ui/nifskope.ui b/src/ui/nifskope.ui index d3a2eae3d..2b85387c4 100644 --- a/src/ui/nifskope.ui +++ b/src/ui/nifskope.ui @@ -1360,15 +1360,18 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 NifSkope Documentation && &Tutorials - http://niftools.sourceforge.net/wiki/index.php/NifSkope + http://www.niftools.org/documentation - NifSkope Help && Bug Report &Forum + NifSkope Support &Forum + + + NifSkope Support Forum - http://niftools.sourceforge.net/forum/viewforum.php?f=24 + https://forum.niftools.org/24-nifskope/ @@ -1376,7 +1379,7 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 NifTools &Wiki - http://niftools.sourceforge.net + http://www.niftools.org/documentation @@ -1384,7 +1387,7 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 NifTools &Downloads - http://sourceforge.net/project/showfiles.php?group_id=149157 + http://www.niftools.org/projects From 4b961471029f21cd6d794b10973c3352a9c5a1ec Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 11 Jul 2017 12:22:07 -0400 Subject: [PATCH 052/152] [UI] Icon changes for light/dark Icons changed to work on both light and dark themes. --- res/icon/axes.png | Bin 2041 -> 2061 bytes res/icon/bulb.png | Bin 2725 -> 2602 bytes res/icon/button_view_walk.png | Bin 1483 -> 1615 bytes res/icon/close.png | Bin 0 -> 1281 bytes res/icon/collapse.png | Bin 1318 -> 1896 bytes res/icon/collision-ball.png | Bin 2425 -> 2427 bytes res/icon/constraint.png | Bin 2756 -> 2741 bytes res/icon/expand.png | Bin 1342 -> 1902 bytes res/icon/hidden-disabled.png | Bin 1986 -> 2189 bytes res/icon/hidden.png | Bin 1631 -> 1791 bytes res/icon/light-rot-horizontal.png | Bin 1991 -> 1996 bytes res/icon/light-rot-vertical.png | Bin 2019 -> 2060 bytes res/icon/load-dark.png | Bin 1537 -> 0 bytes res/icon/load-view.png | Bin 0 -> 1751 bytes res/icon/load-view2.png | Bin 1519 -> 0 bytes res/icon/marker.png | Bin 1949 -> 1935 bytes res/icon/node.png | Bin 2351 -> 2282 bytes res/icon/pause.png | Bin 1228 -> 1268 bytes res/icon/play.png | Bin 1404 -> 1648 bytes res/icon/redo.png | Bin 1452 -> 1905 bytes res/icon/repeat-all.png | Bin 2615 -> 1960 bytes res/icon/repeat-single.png | Bin 1522 -> 1623 bytes res/icon/save-view.png | Bin 0 -> 2175 bytes res/icon/save-view2.png | Bin 2020 -> 0 bytes res/icon/save.png | Bin 1117 -> 1139 bytes res/icon/screenshot.png | Bin 1495 -> 1663 bytes res/icon/select-verts.png | Bin 2036 -> 2159 bytes res/icon/sun.png | Bin 1880 -> 2211 bytes res/icon/textures.png | Bin 1244 -> 1365 bytes res/icon/undo.png | Bin 1459 -> 1927 bytes res/icon/undock.png | Bin 0 -> 1149 bytes res/icon/vertex-colors.png | Bin 2238 -> 2185 bytes res/icon/view-active.png | Bin 2222 -> 2302 bytes res/nifskope.qrc | 13 ++++++++----- src/nifskope_ui.cpp | 2 +- src/ui/nifskope.ui | 19 +++++++++---------- 36 files changed, 18 insertions(+), 16 deletions(-) create mode 100644 res/icon/close.png delete mode 100644 res/icon/load-dark.png create mode 100644 res/icon/load-view.png delete mode 100644 res/icon/load-view2.png create mode 100644 res/icon/save-view.png delete mode 100644 res/icon/save-view2.png create mode 100644 res/icon/undock.png diff --git a/res/icon/axes.png b/res/icon/axes.png index 87bed73d49365427886e8557e5b2d9da6c6407a6..fb98207e16cbf51e4351fe99bd55ca93478bbb57 100644 GIT binary patch delta 1323 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 FV1mLOL6iUh diff --git a/res/icon/bulb.png b/res/icon/bulb.png index 12d8096058242717d679b9eaad8489f8578ecfd2..714faeaf4826d828d218381299c0beaa9542e1e9 100644 GIT binary patch delta 1862 zcmV-M2f6sA6{-}FNdbhhN)-YhMMgw8LNG%%F)&6kF-12*K|@A4MKU!vI7T@(HAOd* zegYjIMMgw8LNG!$F)&6kF-12*K|@A4MKU!vI7T@(HAOd*x&kHyGB7bWlivb5e=%V< zIW#e1VJ%}~GB_<}V>CD|VP!XEEoEdiH8444F)(8@VIm-NbW&wzI%IESb!}yCbV)=# zB4K22Vr4pHZ)0g>I&f%jbZ>KLZ*V#|VPs-AWj19kGh$&kEio}=H!Wc^V`eQlHeon0 zG&VCeG+|^SFFqhVFLGpNIz(l2lZpd1e=ysWh5!HtJV``BRCwCVSZi+-RTzHfx|i;5 z3vF3iR0=H+(`ZBD?SmR1Q9o+rckm1N8T<}S{HhWY65|IFHB}-a7DT$Glyxtiz0Azc z49|PcJMGf!wzI(xO!9PR=e(C^-rG4_BS{j{*AF!2vIIF`3Mc~=U>Ya^MZgi+e*w@1 zc7YbqmirFi1Ce0Fby@wj>F~=EX9XcJ3!DV1g5yHY1q^Y@1pWiIfhU5x5bTNFXq-bk z1DPptya=2ImMIR;Q+?wiwVs@!UNc92-%=X6d_=iQKr;(9np^&vOy?e+{tY|?YC^Pg z7^!iDOj#=k876X`D0z#v|M-Mze|MH>5Ljd)izb3&mnC5XlY+WKt=c)-{Ov3)u0Evd zo8KXLrJ=OOKq82<5;CM_W`I+`1w`e2+W7HvYHU`>1kc*AuBdW2Pm6nt)g_#UFnlybR}h6OTnlp*<+&PO0k|)HM6z;nIhwWh>N;* z@#$dMDrrKdn2CjuS=6aa&9m;-`lqzG_7vZqNr88DNV7#KFX>vKptDM~yRWM>xzn1A zdrTAJ+A$$&!Ayeph+#hle<6!&%iNy?5t@%}%&n}FJN2)0U{fL$9dtgbyYkZJReE;+ zB2rye`HdYs|j#Q)F-2` zU)07>EhT+fceSn!^jnTh_k#GaMmZl<)P@M#6!Orsb!!{)$=}VPPxbu3vs8y0R*eOK zXO|9wCBBU;glK4h#ps|!b*GW;dC7}XiSgpSX$<)hJwxW8<-}q)8XAsA5{hL;_-bb4 zOQD)CToXyCXARp+f8wFNGPNDio@^?!r;AUvwZGAZqbBt@Lg^;ipz?_Z#lCLcD3sO? zrB1kTQ=*~1k0ZUNWPDxNtefDIZLv*lyPuWNKnU^C;VCxBb?7A&VPr6P&L{`IcF2xW zlImX8SVqbAwO$e>G9p!yE71B-;)sLZk;C%yhIPh28TK9Ss$PMO~61@LR*;n_>K zVB=S1i)cq|e+QEsKhm@{_6}*e(llMTRHoX!Rq8ZLdd840=HW@L2sJe-GafCzcL(dY zvHBkvEN$79DE$Nc1;fA7YabaDz-9;p0>7z|eoxm}mUUQ{=#kj!j%)d& zjpB+t8#& z9nknH93v&WkW1%I|0ps??PUi^ac9&UjUMedQ?h}QCJ=fWDH*J>49m(-piUMtlM+&0 z*J}B6fBU{ekNF6a&+SJ?S+mtMUSk=S9qOt~gzSuj(*3R3Y=-#bQLPryERI^$>Kv6y z+KlH7X!}tEsfwuI_h@ED9hsY=8~@*g)UfPyLfYBsQWrJkx*^Ta`;^P+K>L1-wHCU| zp<>Y{$5DjF4#Afpv=^ladt|#Ez_CYR*j3T6e{Dm4Cnl(yHi#5KbQ;l9J45E^OiD-{ zcnq8jeczzn-5Br@sV;tQX+r#ui%!R zWct$5(iQ$835Rs!#tqbt{+VvuHeI`RO_6~Nch~job|;%dd)L*gSMS2{2P-QpU$3pL z?V9Gk3BKoNyv8ytJE?tR|37H2#%0ngc0Cth0HcWic!;702><{907*qoM6N<$f|HeH Ao&W#< delta 1922 zcmV-|2YvXe6r~lANda-ON)-YhGDAW`MMgF=LP0k%F-12)H$g^4GDI>lGDAZ{F+@U> zegYjIGDAW`MMgClGDAZ{F+@U>x&kJX&LP$SO{yU%g2C+i{hYoC_YMv zQbDPr)LE8M8Z2~Q6jRDUgo*+99BHrC75#Tr_G+Zj9&Q&Uj4R*UfBQ_Ke&R7KB{@h?%>dpOpMVarbmVm8xI2b=jmPOA8o?N z4}L~(nHRU8W}sCl%T3t-^&2$420l8if{*A3$Tb_kIG$F$pgv5l|qLdY*?`3aInso`MMwB*Zid@f;wKx2%C&1}YfHt?5vj zT&J;}CttwT-J!MOMl2@TokbyAnk02P+=PSuZ55`lrD4Jq4Sz(r<06oUW=b?u;D;6n zT?7=)c)KIl_369-D>~x+`#X0#gy5nE?XJOWusP;!zDeyd)?u)BqF`btYMw znsG!fl`+2$^;jPzKC26_m@?yu`ZT?;z}yL79tUb1g~8!7-Miwfo?Fk%7@Av^1r(}X zQvg>isLA}UDBmC|XgMk8u3xB2r?jfQxpii*T{#DjbAQ;J7vyv1n3>#<2FYPhs2_>!p z9qa2N*YA{0q7$3OmUt#x(=I=@z)ItxQZzRMlW@U`V14@)zA7QdTN0`>YJx;hND~=I zb8+Sc6dhnar{D7RonYVgv^A*|j!hCI?eiiED!L<}HBc_fCZ{!m|& z4YH+yBCBoW^MGbUrOUhC&)oR@DFki;Io@XYI3MbXl#DFl=SqikaSV+m8+vbKTE|F) zE|WlDc}VW|`pO9HYqs~EfsZj6_o$O=$w>O*zJITunS0WuG2458@ZPA)Hl>%OhbB9{ zdHG|2sP{Cn2OYreuWQiRzYfFhvaT`QE@Y)vTaT`m=E@mt-TNEgckt~Sw$&GnHL*N2 zv&syV3|XJQij1A0;X}Cgtph3A9iyZyNmq-xH}6Q7#?V-8LwYE-l3H>2*0PsCBLVep zx__~ctB}0Y1y?Qm5pOj>H_`$D>t=gzL;&>e4%~2ngo15(_AYw{H{Z^@fmPMNZQQJT6JpTaS;2xSe*P)kRYhIGe3x}3=K`WF0OAlplF z$5l|SCy9$t8T9)phAWxz5QVeRsDESrm>sdoo7Ac~bG`E+AV)x%XL+sG7YLzgL`YkEfS zNQZQ-=2@Hvqf43o2kjz<{nO>VV_|3(9X?$AxbP$wIsOx103`qN9HW}8H~;_u07*qo IM6N<$f-$6tod5s; diff --git a/res/icon/button_view_walk.png b/res/icon/button_view_walk.png index 9593a134bbd5b8441283cc0235ef66a304a2f0f4..2e619058aef09ebe0e7ef3ebe199703fdf9aa677 100644 GIT binary patch delta 946 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 JPDHLkV1inw`mO)~ delta 560 zcmaFCw~R}%Gr-TCmrII^fq{Y7)59eQNDF{42OE%-|NK93qT+T{Q{7|(BSQsqOFctV z6H7xw9R(u;Lt}kI1AQY)T|+}F6LTv=vyFcf85NvO%q^T8oeWJ}E!_+aT}_2QzsnyuVZg(;Z1d?&TU-O5wC(xc_4sSmUXMvf?B<{MsTW}BdH?%cic#c~ zO1pQnPQK#{2~E|Xep-CSOpUIX^Pf3d0*ntbe&PF9d;9-2)waY8ao2-OGG85vZYl`g z<{0x@j_pX{9+m_C(^5Y%o-6g4G^McO=GX5Gp`nTr!9_CZ`Lmv}GjRU+$I`*__WiwM zvt+lZaxmS$v-_^!!pvVR(nN`{kEgJWZWupLN^${H^G=+iPuR zpB3P9qLPj)0K`}QsMKUrmHbF8q zlX?OiAVoApHZ(;sH8wCeK`}8!H$p} z#z;XWni!1|g#V!agi%0^5Eay7H2h%riAFI9qL@ga!jxvCA+%lVT=xA7fv=oy)DeCdlXW$#Z$Q6O@7=xG+^gf3es| z{Sd?EfGm-**DyYHiPmbGMvV;(R9{s^7A8%E!*o6npfdvlOOHz9~57gGuESzib=q!Y}$uwz+WsyGkr|aG~ zjOFsg@OV5!3DyU4pas+@Zyl9se=_9-f}EjXP_HENq!?Qx2ODc@s1m|?G)e+uL?S^v z9w!lr&}?|O4kFJTVZC;RxSdL@z-a}v|xKEXYLz9Zxxs#k!N`ub~d^2^% zf{qY#67Ytd%jLzSFnLlvT#`~KyEvI3X;I~w=fNUy1)QF!=0w}unJIG;`9qrKwT0-3 zxbB?u2u&^L*CsC%BMU}Sf76C4^sf_ztk8l5o>~Py2BFc({2nUrMj~A^O@nmZBGa@R zI6rzkkyvpS6YB7cuELa5LZqN9W5kCAeLWRH(oTYV!9!pz_;7SZ-+ z`t>Xmi)m(ky@$MBi%Lt)tIL)>*;ZBM({}GpUpR5X3TxW9nvFKgb4K7;rD%W+dCloqb zcI?=(g_f0Gm`Ln*bGZ<5Cx&>(AuJ6tp25N^&;basW({dlP!a3{Y0w7d%CTmI%#a0A z2#%dLcX{Q?rfhk++})EEhY#_oSVCM^Ce>KM8^7I{kzk{_dS_*zs zF&-)`1W#uMOV delta 1560 zcmV+z2Iu+v68RF4NdbJZN)Q4dHZ(axLqSACGeSZ!F)>9qK}0w~MMOhEIX5*lH$pQr zlX?OiAT~5PLPJ4BLNh`_F)=YkH$g-=K}AGEK{+=yG&e#sGn2LgBmz@5li&jGe<5kexG zn3-rKxS8yq8UC4nEKz1qquC$JSmGo512Z+40fxCvqy zGzNlT2)w{=1|NXEKuOm!T+Bm6A}&@RV}~K8#^rEOMM(+e+wEk8!{qUL>B7VW1vKp$ z3(;YkwO(RSD~dwE5B7pq5Cw08^lBklb49z;UoG zSq8>ZyRA*g^LnlDql58u$Gq|OxVOH2Ydh$RtMzAy;O$PlCB z3P~qlSJhf1f^NhWA}JUQrX%PpGp2Td&rBD_Hra%1!mvR~5Ki~@ zl4o?3Mn!SnW|;Xg*DzWnx7#3(C!w7P0YrbA>;@xXBY2I^UF7{KHr5VFL$F2!?vYWaPC1F*eN|Prt=F{cbl||e-j=R~$gJjXf^Wck z;EVFEE7l4+eJ#r!x`j;+Uf3l7@G?Yy2*l|vpnQ^S>?tUY$ zr{^+-!#@gfT#>O)@&+p;)c6N3q3W`69^3mGbYVf%z&M;gocPWR*T0DG? z-@`W@9v-IZ>grQslD?J3U4ch=Ushqf@ncO9UUDklMQetyQAM+*-|&S zI@3Sr-2buRo)fg_7#bWL%)-Ua&)HQ~{FqJre;F|qUW%aq1Q-CYCA02mf9rYx0000< KMNUMnLSTZ%NA5xZ diff --git a/res/icon/constraint.png b/res/icon/constraint.png index 86b114ceb2bcf9af56c943fd85e1db41ec41c3e8..f77c705ac3f5ce672840af865e160a505760bb29 100644 GIT binary patch delta 2009 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 diff --git a/res/icon/expand.png b/res/icon/expand.png index 41d4650f30697679620875e437c6b7369897aff9..53ad04d9aa676b87cc31b12053fb8bd3e5c81530 100644 GIT binary patch delta 1149 zcmV-@1cLj%3hoXeiBL{Q4GJ0x0000DNk~Le0000W0000W2nGNE0CReJ^pPRAB{nT% zF*7(IH#sgbH90skG%O%8FfleSFgY(VG%YYOIxsmpFfp9qI5IRbIX6TzG(M)EV>L2jH)i^U9YaUXx4iv^eyGCzpHQ z=Xt;Te(t$DKAB8%(#G>V_ph3?KYtB($L-E9;GSHvwzih$>j-!j3WdT-3Pbx4hgF~q zT3*!*6crT_%7D50`g(o7or7Bn%{yE!Ch} z**f86oREGV84QMorluwb3K}Iey2Wga_X(&=PoAHjM|?hCUo;wB#_0x`U?wNzb67EL zQd4zxb)Cs%5;Pi(E}z@*@_+I&j*$zSo13d_^a-_AOv0}yv=2D7T5S^Gav(`u(vUX2 zbHz@W);?lHoY82UuB@!I$q{#UcBa7%k|v+RvP;n2-7Q-2;NV~w@z#!x4moPV1*;Fcmb&$Q~T zIBi{P0Pn!S0Dp3F@?~abW*8gtIF-lD93wVDIpQGZVVbMFoE6JmfSx^!QfLOqiubnB z{C@ukMtcMt;&vNGXp3^-H~?$9mRYglJH-2)W|2Y&~Pl3Cz^ahJmlcE*le>ufiPUMo7YTU?*tE#FRdV6~vc(5q4;&?nB>F@9F3x~ruw_>x|T-Vvz zDHIeG_IkZ`JP$Hj@$T;Ka$8%Q6Hh}wP7iPU!z6;jfv&DD*Xil$ zwsho(Z6W6-PPWdoekw_kW67T2wcEb3vJ%B~CkcT-AWAXHsqeJQ+vl&@Rb+uPp- zgTceKwY3PQy*3B2S8|@8?E-j}AEza3v=7+%9uNY4Dg8L*|NF1wKLQK@84vONAk@kn P00000NkvXXu0mjfmNNNo delta 585 zcmaFIw~tG)Gr-TCmrII^fq{Y7)59eQNDF{42OE%-|NK93qT+T{Q{7|(BSQsqOFctV z6H7xw9R(u;Lt}kI1AQY)T|+}F6LTv=vyFcf85NvNEX)iHoh(gUE!_+aT}>RN(iz!~k)W9s!+|1la*T68vP&e7!#7s9SH7!Xu$<)}; z94KjIZfZK&mbs~Z<-4$K1_s7tPZ!6Kid#!2pIz1DAkijm%i?&t><~xiB`>83+itq8 z@a=xV7q#t;T+U^)U6RpTFJxc15Wo|~vi6AlvYD4fOs{@)V3t>wfB*Z$cVh;{ke|;A za*~W@YRo?}YnkTKhN``JzRQD8JY#QWw3&ZCOml$-7bC-lnP=0=Pt+G!w6q^i6lQ5W z@QU$v^TiC2D_N!yYs2>Hoc{9oV+7~ng2%u2O;I^1_xh+hGowx0V~f9W>&0L0>f3d9 zoi<04cZ$WLqe&Ojzv?pxZOfJ3q~qS}#(ek8>8F<}me+c2xt-g3=l0uc2U3sRkXaw5 z-8^m86eg>=emAnVD*TLFe?9rKN&WFFKC`5zF@=P>N<8RX#PNR7>YKZ_y@~vPxbJC+ z)!R=t`O7l13T2iDGt8_PKK?FscZAaU&rkBJZ`n*|TVxp#^s9x)5l+*-OD@}zg}?t7MIgs?6UUNo)c$eV`z0siEHB7mJ3WA44$rjF6*3_ G5}E+~f9f~@ diff --git a/res/icon/hidden-disabled.png b/res/icon/hidden-disabled.png index fcac0f79e3e1d960ab2f76cd44ea50286b1ae9e2..e65918361d234c3219e14bae0fb2acc627925551 100644 GIT binary patch delta 1356 zcmV-S1+)6X4~-F!NdbhhN)!ShMMFh2K{!D|HZe3tF)>9qLNql&Lq;+|MKeV~IXN^k zlYRmmAVot(G(k8)K{hcoMlmr(H$pTuK|@9|K}9n~K{+`zGLyOjCIm7tF*lRn0y-l! zH8wFcG-fm{H92B3En{RfF)cY`G&3z?WnyGGF=jMmWjB*N1Cf6)otXas00f^&L_t(| z+RT^lPg`Xa$IrbjEww*rq0qII2!U-*h?&zGCDS*1Lt-LiO(h2%XAB5F?(TkT{0cC1fpRP9sOZbTAG!%^wJ-m=h|+!x6kblBki8#%jLFD z&+~cCbI!TvGTDD@7M`Rb|M#!FmTUH*22tlx+$%QQqc5CJC$Kek0D<`s^)l)MltMg# ztpkiQcmF(ra^AhGs0WZ32Be@EKB#v$hNNJGV*&LMq*5GPC(i)IQ>S+O$SQ>)BZ>O{ zaoSfouFlMubE!LkdIn|9edR&@)_(i8`-O`a56P0rY=wU?ZtJDk{rDS63HUtyXASUkCQ`Wk~6C5Z&4WE}1N{#c4FA&7Ph^ zhWq!OOgc@4DGBuL^mH?1GV}PaP5hlorGozce$ean0L#k&6BD>1hh-#1g~^{(La#Tu z#>e-DdwP8KW5=lLQjhGLD_3M_Sq3=DuV5Ga5Nhr>nl3BlukVOwI9zG14W-~EPGeP7g+ifXn3fhE29!!jkB&m{-aTTtlv!5t27z8j{X~l$ zQfCfcy9Qh|%EN4IY+w>*Ae$2g3lF2wXn21ZID1wYGg5MpH6>#xKfbtdfmG#B^Y<-y zOfSvPgA9!{Ha7BcBlb*dYb$8A+D92(z=Z823rqG5tbplQpWU)FY=)n=>rC>hI_V6!K<9HW zYjuSY0&%$pexaT);Ke4vC>^3L{k*^H6tc4fDADc!I`10t^7$8>qiCK*vr1 O00009qIW;&kMlnJ)Gc++lF+@Z) zlYRmmAT>iaG&VLeGc!U#GchqmH#s#pGDa~%H8V6ZK`}%`HIupmCX?j?IU_SLH8x{4 zV=*l^IW#dXH)S+sEjc(eVJ$K=Ff(B}WHC8rVv|7wk$=Xo3daBd1KCMLK~#9!#F$-3 z6j2n%&&F62i-8DZaO1O}BtS!g6d5M=MZL{tw2Dr)`NuSPv%shksxYggx3jP&?#M8wVJwoSGs_Dn(ct zK5Vy_L~ORA+;Dg*KNy_F5K#u>!`$Opg*`4{E71!*H42m2M2@qpI-Q3W^7BumaGVJP zjmzf|bYL#9jy;M^7YYK^)zwi|RTX)HLHgFwLE*A8^85X?y0*4yuBg?z%kOrd$qR)> zbAKX{8%i*mD+HKMbOTB-ZnvAdySu5ev5|`7aq@R|5{Et#jnW!cO4WO z7=dveJxasF!+Jo>Rzuo<6dxI&-l6MK%fKwp7=dBX0|NtEFpni$`gsTgS=e_0MwD*{ z42I`d3oh**OuQV+q0G? zC;x^qjQA`yqFOTd9g}&lAsj?kt*@!@Qp(}bYN-LyRc8aKH zW?Zq@&t<1`f5dJtO2CnHV17X_pbmgc8_h#PHZ`y<9uMtWSs9I6EMtQe6?_wbIV@%O zLTShX#Hzm`=oLT~vMrB=zbVMA$)xY%moMZ0R*0rcmj=V zHH=o2{gMj!5i?=rVXp?YWiU#4NDj57Flvc04fScOVYIdm2GwE4C%)eT3;?xE-^?Qm R8UFwP002ovPDHLkV1iFm|4;w` diff --git a/res/icon/hidden.png b/res/icon/hidden.png index 6e740d5cf89e1ad6e6ad304fb414cd1d6f003104..764291e46fcf64334bca24642534a9c184686e37 100644 GIT binary patch delta 955 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 Q00000NkvXXu0jG}f+Dyo)Bpeg delta 1185 zcmV;S1YY~h562IXNda-ON)!ShMl(V%MKw4yGeSWzF)>9qI50ypML0AuMm8`oIXOi( zlYRmmAVxDnFhw;uGc!U#F)=YkH#jguGDSEvF-A5pFgZCzHj}ynCX?j?IW0LiVlpvi zI5sUdF*9W?GGa6_Ejc(bVl6N;VKOi>HZ?J3GBF|`b97Q=W;$e(3j#Qkkpn@06brXb z000B*Nkl^58+lzgDj zR!*{90CnJ)o13#)5dnn_(en9xTk}|3TcaG%xHKK6`@r<{v{_jPhhH;#2i|MI2?G?! z6wad&`M?Misu7UgD z4j7`SH~*lvU8z($=~4NAdWo+$K&bf|*a08GJMir<17LCUei%FkLEWTpJ zuBz%5_J9PQ71{GLQAmBO@@}@5t!ii?v zDgB8=f)wB6GIZ&B!a7$#w6U}(8>2>ammAS}j2%w!wnij>qP9xA;Z*);k}PN zw)2__oHJ$opMgHQ-$NtX;fbRLWqoXb=~E~MLfl8E-`(BaL?@Z0rKNr}Diucjjzl6U zQ`<51cQYWlN|a#O?Gz&!bVvjCA=u-{LFXj>cvZddK@IFa70Mwb8nIbhZl&8vjh4tTt8yg!`4JnSo zWrmV31@E>0@^#EnXnc@zX{Pj_YTb-K0t^5Z@0o@W)qtou00000NkvXXu0mjfVP_SY diff --git a/res/icon/light-rot-vertical.png b/res/icon/light-rot-vertical.png index e2f20360913aded7be38190149b108929f1c7622..505be06d1f1a35edf514cc7e56049734b81e3377 100644 GIT binary patch delta 1318 zcmV+>1=;%J4~!6yNdbhhN)!ShH%3J_GDAi)HZwRzF)>9qI5|Q%K{YlxLoq}$H8U|d zlYRmmAU8%uH!?#;GBz_fMlmr(H#j*$I6*ZwIYTi-GBqK-`GB-J9VPP^gV>MwSAait5Wo9~LZ)0_BWo~py zL_H#5WN%_+I%IESX=FNZXm50Hb7*gHI%Q@sGdVFeI4v_`VKFT+F=aO`VKZZ9EjTt| zI50FeGc`0}WFjvae~a(;@&A0bml!C!xFu zT3B9Q9?%>B#ser9q5MT>XXlpJ>)nF#=lOhI#Js2}09bzm(+B`$0pO|p9$t6`{1VBR1b}}{kPaaHS^~IBKLGHydI8GcfM~v>2>`gi6_cCY z|5*jV7YYN&Jv20A)4~H@cK@#3ZZA|XE-g_d7Iu*j)Fb*Y+y|znrY_2Rlij~vYY*bT z4du50q8~h;uA&j?K_*b+(b19HU@!>5U@(E_9`c65gJp^mz$MD%vLK408_$0fvK-+e z+%*AEC#xDpPz$J;6w5C&nap(p5DJB&*dwX{6_6WIfDizp13XT}IsrKWr6^tg0syzk z|4EsH6$=W>h#}{4xok*8p{J+kEA|A*VqTYXNtesz^Z+WPQYi;`5-iiiGf^7@i7_Za z#3q3iJJ_uxG|7w^RwT4wWV3%+!QpU(u%}ez0f}=ULU}R+Q8Zgg%op>f`{a$nWCHvo0yo$ zos+bA24JcIk_?N0Xt9I5E>g@cGT3_IU;GR}x~N<+olaA-P{v*ZC2D_#000UQY~0w` zXhiQ<^@7D>F);v$P1h=GIRrEk6oNP>ih~zzYDowHClK5U1r%RZ04URD@)EUcMF^^s zuz_V#KEy?4jB%34WoTnMD??nnlHhGUI5=p>e9S^5g=IRU(b!G^&<6;BOHqc(makfd za@-#>q*(=HGQ3}TOJXF~@Zq!0i=3_Nyaa&i+ow-KEkY;ujVWv5nP z{Dj~E^no_)DY0wyitMhK&E_`p0O*jjJL7Gdz#;(_HJDp!o}SAHLW@^4UH!R&utU0pR+uXlHMQ=_5_ zOGOg^eSLi~DBlGLGiX=^;4lD~>B54?-Bu%|FvcS{C@u)lz#vS3d6j@{qyYpk9geE^xF`MAA@cK#LVdEXaFCj zdX(o(iB2F%dZa@6r`f{7!VUnefDE8lu>K`<8=#y%On3gv*D(}70nyn%%ctKu0Iby` c>yH2f05u8r39F5Ym;e9(07*qoM6N<$f_QW;a{vGU delta 1213 zcmV;u1Va0a5aSP!Nda-ON)!ShMMN@1Gcq_bGeSWzF)>9qLNP`|K|(<_Lp3urLpL%- zlYRmmAVownMKdxuF*8CzF)=YkH$pK+LqS48HA6KsG($HsMU%P$CX?j?IW0LiVlpvi zI5sUdF*9W?GGa6_Ejc(bVl6N;VKOi>HZ?J3GBF|`b97Q=W;$e(3j#Qkkpn@0+odp%=+d zXf36nrKdvwfYd_87NqT=x5f~A@lr~PB}S7u!zW_JxQ2c;}sYX5N|E zcP+9k3+Ejc44ch1=W@B`JsyvL;Pd%bfwfdBb#7&%aIq1$TCEQO0YnH1Q50Qxzj9th z)(dz)fXfn$*J1b`gYNH-jEoGLP5|QpjElgFd_KRG$z-VFJ>73+1jdL546YZ8#bf0qlJ6MauLBlf+)M&@+2c2V+6j2S!3lZC z6aol)0GB9?Q}{ogDko9=1V}(M9*_5!5rH9Y0qJtNT+>c&Zf@4#eH!GbM+|h_2WDnw zI+cxZ_>)F!;C&LfMFb%8;W8Q#MaW6;`~8t-vnhl^p&XvCQ#4c&YymQi=cqX`S=xpw zqh&~-UaxE4*#RHmVdF}Fl3hdi1+?A+tg-2BB?ugl48~Q+1XS}xq6`#)zYL1iYE`)> zW4&xx8M5LajD~Od!Mc{Dt za#&?%S^^|~7HYLx7b%B)7HDW9&?XWL2D6BQ03v?K94Z9_xB;(!Dza`SQ4l3b^5Z7J zhKO}R<(YbP@yDlxHM;wWxgz%huS!N{uL(!&tYWi81eCqXve)|AXtg?sKqL~W7!gP& zlhxtjVUl$k$dF@)9N8;GLPSk>+K5Q8aF#sD0L?SxEGJ2bOAI&Vd3m>QPOeN6!@X6D#I?uNf|=%F%f|BFq0NEWk}l-#xad5rVN>&s{AY}p1NhIG>f0AMnsGq z03Eel$bmd?qtR##Vog;q0yvxuI-Tv_bqJYm)l`im`p-sxq>MT<%(G`i0(zYpct3D6Ktyo(&v`3QH>)nO&aa;pf^G#2{H5!Y>9NPIrB0-HRiaepj zOod6qo%e{U~XKswcziHZ`6rOJYbnuv* znws(}@0XXC{V+ZWQ21Y)Mj)L|@4?e|fE!p)<;MbrAN&6v;omd$-w@tE25tkh^Yinc z@l$F;Y0iQ`19)Wmui5tY_71#X0qnpl;3eI|_zJu%-}x_p$MF6MK;MTs{`jl`s~6Gr bTYv!o*$2DGQQF^}00000NkvXXu0mjfdqEu^ diff --git a/res/icon/load-dark.png b/res/icon/load-dark.png deleted file mode 100644 index 8fad8d1aa803a36d31dea1d7a4a57a05e2080400..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1537 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 diff --git a/res/icon/marker.png b/res/icon/marker.png index b6f621ec1c1144cb39030a1c046d5fbbb243b383..0ecca031a035149fa31adf58b0e86d0b540e31d5 100644 GIT binary patch delta 1087 zcmV-F1i<^9504M9UIHLQL^VS+K{7EmF*P$WF-12)Ff}(rGeR^rLq$O~H8wPpegYgI zL_{@1G(j>jHZe6bF)>9qK`=EpLo-4&HbX^0H8nOglez+ABxE#VGBY)GFZp(6cW2)F=Djy>#&TU(>C)cbuByq~(a|GlE(5K=F@K;5 zaDd;y9Pk+!zR}h;TTMqUm)rJk#UUVuZvz(rTkR}VI1Ic3kzXbPCS^2!9?dhLgX1%J z90Ib0ZL6$hsiX2OBa4!;y_nl!(r+7#UqkZ{NN2?8fx0?X4=4i06-Zd}jsLO%krZ$r zI<6-ofQgsTbaF)*F7IW$)|7FFcUT(W;0R+~) zw-WFU7!wYyvO}6V`mjC^gl2=?(V2DKIg>WB9%b#eoM)aR_`b zCokgeOGEJQ6tKrY;9O{qa(@B>KjRRX;G?JyLMDsCXZ6)U26@!M(O0{6~dEri98 zZe5~Q^MQ%I5#d+ahBukmF!&zrs-I`TS=ox-wzKW192znbK!#62`4o(uSjARKNiAqV zo5p+5OVT^QRbZN%6kzc>$#H2C(CPWTK`~Sh8h?dE!ShD5^04Is z8c&DL4bAkWHW0Lb3$%EGYr!Xx22kRfJ7u%;JR7n8FPs{bFUl! z3cJ;3Cn$0x!Bg<5B%Ggg(@AtCA|NC_qIph8Kau9qIWsawMngeFFh)2sFf}nZlez+ABsOF)Fg7zXVJ%@|GGr}b zFf=wTIWagnEj2eZF*7(YHfCivGLu3Bkbmqg9jgEU1FuO$K~#9!v{y|?R8bVZZ`3TA zF*S#1V<9c1Tm%C}5P{I9O{*xg7PSciK`<9Bg`l5J?TS`yA{C@sMG{zT+EmiQ$VDiY zIR5|Szs}6-`{tcD@6O|mb7!;z-+ON^_n!0JbI&>VjAh%lGL@$XtUA0}fF|G=5Pt=> zfH7bc_z3jenzctvby_9_#P9>)5>S$7dWs}~J0S9V#{^*f0$wkG2!BT`r7A;8m0J!I zu1?sF8Lm+>Nd zYd>~;Fr*5SNqA#1l_7BjBzlYq$bXst0K!4WH?U*{ShJl2-v{qh2y&3%aU2hdiCp`- zPvdmEZXBLfaXdzWnUqIj4&P%*2V2^HxHjUD3Ih*~Jb>LF#%q}5>Px*d=d3?4uz$!kk14FBFG_Gj6noI~Z@=>k@ zl&0)GFrpjx2}!*1tZ}zH+zWDf@1afnWGo*9d63qNZ7pC7Oa7gXvXi#hM2- zE}JFe21eEz87RD7lBv6H>6UGHNRF<8itW@i4#~GGFGNCF_I;-sZ2T!0I!1# z1*V}Cnht*RHcJG|*&{oSSi#1*X2C1T9nhIFCLknO>qD-2`3g}x zFt=o9?Iy2quAKcGJAb0>=59*>%;R{qy62fNFrJXPBCXIdUNiwA(dAyVQwB#d<+;8$ zUqfnq7mx?bz_Oiv*9Y~1%Z}^gvPSI+0qkUkjU|iDqO@Xu;h6M)bU=%&QM=^`OWgN5n}sUk!SU6X(#t`=jwXb)(fV5(U#{-jtP{{$ESPIHs( TxIoKW00000NkvXXu0mjfoQ>bv diff --git a/res/icon/node.png b/res/icon/node.png index 49df9e7dd13e4aae0b19fc7e83b76c168eb3663d..1ebe33dfdf931c28aa2e3e9bbb1d685312a42eaf 100644 GIT binary patch delta 1546 zcmV+l2KD)`66z69qLN_)wL^(k@LpC!qMn*I@ zlX?OiAVoMaMMg12Mm94!K`}8!H$pcyHAFcM(rF=JyQAait5Wo9~LZ)0_BWo~py zL_H#5WN%_+I%IESX=FNZXm50Hb7*gHIx=EpVrDsEWi2yeVKFT+F=aO`VKZZ9EjTt| zI50FeGc`0}WFjvfftPoJghjA47a5~%|Kp-0=;vdjNTwsu<|&q~GS1@V@M z=tkrP#LYZjKvW}c?!=ZUgklonlHiTfub`uLk%4@Q@rCvvlwo`}ecr2x5yTxHBYaE| zPC+lTV&apb^+_ufXshA&qLYDqiamc6pGtYTh?Wq=5YwZGk9lS_Jj{G-l+Ot=PM9+6 zA*$X?Y)}DHQ&X_8u)x*~1_R{e(&oiH5dAom~6>{?> z*kMfX)o}2WSq_!4ZgAReF_2F&lbRD{T5{|I&nO>v*Ik!*+w=7Dwn+wRkBc!qF)_hF zKE+OD$~NSRO_`Op?DFsDp+H~Fwn;GZ^?E&6?cLya42T(HnzqSqyT`Qf<%~unJZ*ef zlxhEh=$tX1zH$N{D|&|Y484DujEY(3oIq=SxnY~cZLlu2WuH)^{kAv@mY)k)1MA{7 z4CGrz9tT>(Ihf*e#mp)+FntkXHz6T`L16kvyHAv9e}<^rtLQA6U%HXUPtyjA=5(O7 z*1)a&FI*A@F{V%AG?kf^K}Bf_=)J7~HTDdF`~#yNxc1N3U*3#q|8pp0FX+#`*k&@Bj{Su; zIXPL@)6+Au$vGr-1qn9J@5W8hyUhQhBx`LACZVj%_DXDQ>_O}WQ)+7Jcje{fdjZEbB!S6A1k zszyUluKMWFT+9WB!_hCSTP&81NtKp=FVLufxw*N14lO-B{iA=1ii$mfImN}rM%>;w zxm@mZxm>5WT`}V6dt_y0WnmXuQc_ZX5cN?}jmQqSy1Kg5+uLi~_JXLHXJ=>YXyUNR zGaDKj+Wgm5D%D|yLIGIx_>O668vB`gp6tO-=3{`_+NPG~vW~ zlarI9v6v@l!0<0^8>rRlQzY0rIyw?}gduzo9krj+m&1p%yDKg(j;-PO!!!_%J=5Zt zTR1&E?b>dJm_o#!fzr}a(CKtc0};rl7_Eh=#UoM>Q+^7(yZ9^gu~TNsvOM z@u#(LwRj}9j48ymwKW(T8iM8JWyTDF{NdqYpfxdt81XHma|Ne}X>mGl#>U1(6=J9} wC}K^G4-zKT;(TmBpA*u!|Nny)!u}Ru0HwJHA^h#t9RL6T07*qoM6N<$f~Oqb$^ZZW delta 1556 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 delta 593 zcmeys^M`AKCu8A8FA*jMBQrBg3s)0oV<#sgLqk_{Cs$`9XA4&&b2C#DM@Lhm$#qOJ zFhx!{742eDoBWi?QrA4uz#!Qo)m+yw%_vznG0DVC*U~&GO*h5d+&I-D+1NP6z(`4< zxFjew%_?OwH5?6i3nIiirZawg*wMjpn2~egg$n^OT>lt9)+nrt*jx2gdY7e!hDJxgmHHZnM`8-| zw&dO2b>F0alYRX^fkR9VcbJce|M~OBCPU6CE-r3~+C5)~m_50-w^^GMPMAGg+H&Vh zae?p6i;AwVi;Z9LNO$dTS1HWSTY`IZReM_bKKah zqpQ0zl%c7slhq-n^v{ov(^JA__t*cgUlu0FeCnd7Lk`Q4w&sJ5A0ig6C|bIxIUz92 zQbLBs>&(yX`S$tXA_51t#>RQDYPB|`U+Q{D#EpK1v6Lj#zA&1%x zf*sxse2S}IEm+pPuJM(@0_QTGlG##M{%9~J)q2S~XL1~r&TwcCSlj5fuDOq~kne)x za|iZC(qF_o|FU!}Zn)7V)~9IJGL2!Q{TgYPEVE@*%(+{%ZC@=2WDn$EVB%poTX?I+ T$vZ@v0SG)@{an^LB{Ts5h%fHS diff --git a/res/icon/redo.png b/res/icon/redo.png index 830442d8f9102cdd055307164a406adaf373822c..95174027a7d3ae5b7aef8df7ac31d2ebbe85c1e5 100644 GIT binary patch delta 1162 zcmV;51a9qK}0b%LN_uvGC@H`L^d-* zlYRmmAVfnjLPIk-LN+irI59CrH$g-(H9|KsI5I&&MnpC7I4v_`VP-8cF=aO`VKZZ9EjTt| zI50FeGc`0}WFjvv&3kWV7?x#KS6Q?&WEemP7yy4g zpyAqzz$tJ76o8Uo49T|uXPk_5Aqz-=0dzp?^z`(-?(XjQZEbB?hr@AVx7*LWUhj{t zuCCPh`1s@F<6|G>4BQwR>q7t6?d|Ow_;0D9Lsd(;IUbMS?(FRRWV6{=i^am4&E^VF zo7HM%{eJ(qnVFfp7-Iv}fFDHJP$+-&8UI91+X#WO^4Zzh3Afvw=M|ewCQJkCoqn@?SKum@=U?gnh6^ad>$68RiJ{a%8=}zP{IJ zvx*U5s-i#*xH~#Jk_QI|u~O-;Uoi}$((Cm{*z*LiuGMPKve|58b8~Y7`yYSe<+xHB z7#N7Jt*y0K_@;p*yYl*oxpK+fgCm}QbBrLfcm zs7~#T5b#i_30L3W-@nAITqu7>4rKwnKoGczFkR*e=ybX==%41HNRc8?orpArLcw#9 zfw1=o!*wpvRLDi6(MP+xyNeVx#bQxKXr`#E8{kkCq_B^qygFYOH#RojL?V$TBFTd% zEe5#+N-*Q0&_OeqOoU6T6$F6a@braXFt|brIB&oB@}N3FE`d|0)0ux3GES${6Z`x7 zZjp0?NE{`T$tUoekGw*JXaxZRR|;~@0wc27Q*s*K5h)^KvDiEGU6aUWlu;Q0=>Ew>xbKD=k*L@Ai zd>J)l39P*2^Z7C(BO`NrdwVXWx1gGYV{B~fA*`{mv$NBxv=b&u9bkG~(@c$qA#>Hw c^FIO%0O;tZcfAgoS^xk507*qoM6N<$f=L4mw*UYD delta 641 zcmey!w}yLyCu8A8FA*jMH&aVnuF&kt5Qw_+vcNMtiZ-jt}?+(wPeFXYS-)?c{FqPetLM!+|+UI(-Uj z6`YuiGX5l6d}-Qrw}!}G-Ofl-b-3fS{+2+C_a%Xs6cn!tDj2XvJ#6UuI6GjL9-l)w z%L<{_ERRyZlrWqT{N513+A-ZZ`e;ch1IL?&Wh?8yc(vR(U>{`4z#_q*!IJf}XIcMj P1|aZs^>bP0l+XkK84B@s diff --git a/res/icon/repeat-all.png b/res/icon/repeat-all.png index 69dd05b6fbc0e7f01e75962e9eca0b34249eec1b..cb2d07b341f07e3f1e46d8c62e73fe8829ffd4e9 100644 GIT binary patch delta 1217 zcmV;y1U~z>6sQl7NdbhhN)!ShMKLuuGBra*HZV9eF)>9qLPRh|IX5#xMM6bIL_;+= zlYRmmAVo1XH!?LtL^d!uG%+znH$p@(MmaY#LPbJFMMOh2IFq^pCIm7tF*lRn0y=*% zWM(!tFg78>WYB*p zH1uBwC@Cq?R@uQJaM;ZAnVFfvwGiz=K?v}G3{Aa~pj${tC^!n14(O8cEJuKoV8MXz zAxIQB4Q6WU&+GL43|@dL&}dqrqN1V@6zjn%kO(40^TzD#>_^-$4#in8S!bAqiU}+O z`@vP<0^Nq28ZSveaTB<~JdHB4Up#+`a)mNx+|4>rfpMag7V>yJWQ{T~(!}1Gg5rdt zIT<`vEbj`oI>}UU1Z?}`Gkix6%8+t5z$9+_2`a&L&>{wHcQ_oqViMY!uK_8*VPKsD zV7I8tz?gUcvJezot5Ig3)9I`>dCGiaZ2(CD9_eGNOb4l4N0abVXr*|lGADm0r`p1? z4+EPm?x%}Fq1k$SXqLFXW?^9RLFph)Q@>rO?^nOhmIALFMGW-2%GN0-Yrhc{;o z7$(|Eb8~aw(gpgALa`M@>%2T(F#%etCG3+Z?vaT*^%UyhfyoCQB&CeZQczG3&8H)> zAIIL#K8~LK1RC^~>>*gqofd!b=`xbB;EqK_!odsH^JRJg+M`tsU&OKghQPP{PK` zUB&UOT(P-B`90Nat!G*bktuJ3G%y@|V~?6>aarC;&ubu!Y<-&S1igPHqh%rwd|na!g?5=asE z^?DY1BC@^&1Xjw8YutZsx6zm6W#HsstV#eQ#C;@(s1#w0_n_7<{UAXKkj}?lo|xy_ ztd3<1xS&vkd`mr_o?QkLqT_!CDNKthg{|NLe9kO}u_^N7G@W{KqUTKPAqrJxP^L|p z!lJPU7CQxMy}!j~kq%G_+7<1+poyXaI0oAMf}HAS0}}aYjyqkk90CGG^Jf0i=(ggx zkW-seD3D}jWy#$2H78uXR>vtWE)M#48F}7I;KzXeY+%bi=I^V%!SMIvK1cMR{_(Q^ f|AQ7lehDxDnvK`Hf+v?@00000NkvXXu0mjfnj;=L delta 1780 zcmV$F)>9qI7BitMKDD}FgY|aGekK- zlYRmmATvWnIYUN7LNh}#LoqQ$H#kHxGes~(LohisF*8ItL6f=yCX?j?Ig=g&CI@qL zQe|d3WRnX5IFpeBL4OVfTde>923$!*K~#9!%vftolUEp~h0+40Ev3*2SX~i|Fo+_k zFqk0Vj1D)JY(`}>5ErM3nei71!^F#9|Lg}0T`&$4;}XySvf!48Q;0ImoJ{2I1qP$Y z9a?BB-Sal*Yd(7+#k$3lJn45%Pv7&r_w!X?wOW1NrXlos%YTO7e;`u~_yZP?nVCt<%*^~A(;Jw_d7_Y$JZ}jB0w8E; zXecEqDXEgkFhc8Jhmf~1L^hW`e*8EwKR^HLuC6XM@4HAGFw2`S0xtn0KfWQ4HAJv{=RteVwFmzdgfpiAuKO#YHHFT0Ti@0JUlEvbm&mw zqeqYa#(%UQuy8IDjg5`P$Hm2ctJmvo8ViL&YgSfPAN<{})oO={h<(DqI!Kg@uI``}XZCsHv&3mxU82PSl<}d9unDn=IXwuuKV; zvLMrV>eQ)Xtj7RyKmv#X5g>F`hgjf$1RP^<=YKAelZe>4$eo8mdLC;UlE-PMfGKs{ zkH|8mrKQz)e8Mtnp0xop%Pz-3mih!(<3T`^oWVVN_B`a)P%gqK3HdP;(BI#0Pg}&s zLza>j4`MWckx@IWf9hf{^*8;20|$n1#vr$d3NPWfaD@EeTYEOC)#|C_hU|=MRzePNoSkbh*%p4pXY~+e!U0vPB zV4aKSCmi^&VT^nx^?ShZEbBy%&NkbD_2gku&`Tb7L`@s+S*DnpuT(e zZqUY!8=s-ZEKE&Jy>ug+h_o(sI5Zc8gnxwiibNuTL?RI=6bgAnL_`wiiH(hop{)9= zGBPrnSghI!P3_pR;{gbz5@W=ZEHW~3%h97puZYFs*08X!af`)bOR4PaYzk@>+(ueW zy5O%q6)iu3x{dMF$9{;%Lhzg+h>+m`E`@!zVO{{ZFE!qkj#3 zeSI=k_^=YS1pFZefD_H}& zibdPEZ~qfr!5?ME#3y8_tgMu!rl#J5u#C~sQOYg@Rn$wDE-981Bhc80e)k=@1MZT# z+lLl3oRgEIZ*On!?(OXjacUidgMWh|&CSh&xL`G*bF0YQoLM~`;o;%aXg@#h+`01) zs#~boGq03svDmE9XeQwHuHCzL|91HB;Z!KSfLo@-soNuKj=^m{&`MNRR(8W=GOdBh zxAgS%gc=M6r+ZUpXXiVwW1r}BBh}T_=gf8ty{PD!+))1Nb4T#H@bPv3gTaJ$e@f2XK^YWolb;s?o$YTipSM# zDFt{JYu)VXY9ED#g{p*vgdPNEV@gVj2(jSURE^BS;lSrhSc)2L1J1U%yu#veF%uBF zl)dG0xi4CcAKHt>MJ1;TTn*%oLtexZgeHJ`>>FG1k`E!&WV7g=?tc)-o(wwVDr8PG zS&oEE>=r>T>fnm4CvP2G^Ne9>>+eA~*E2FkuimR(_umhyTfFH2PzPD2H}YqL%Xe(g zM<4LB?Sk$g=XI3z_RW<#?XUb7J+Frse^_NK_r2W{*kZ^_m%UMC{k*i&8z27)FaTm% WY_hnKx9k7_002ovP6b4+LSTZlRAfW| diff --git a/res/icon/repeat-single.png b/res/icon/repeat-single.png index 4fe4237b0a4cc01f94f6301f6a6c8561542df72d..acbfeeef90b31227275a62cbda96ab0558b6fff3 100644 GIT binary patch delta 878 zcmeyweVu25Cu7S-FA*jM3s(bEM>AJPGXo1VLqk_{3nxbxCj)0gLuX4TCr2m8$#qOJ zFh!O)742eDV=*!?G@pE*$*SHUCC$vtz|36Nz#u6}*C5fvLO0RW(o)wbEzvA7Db>^{ z#mq!Wp|~U{HO(p|Ke;qFHLt|e#a1aXB|j+@B$An8RgjTil3$#WUtncwkZNIIo|LR> zoNSn^YiO8iu4`dzl&G6zX=r3_0<^~1$XH3=PQgaMC?(CxCAD~R7qe-7g!jV-3=B+} zo-U3d6}R5Z@XiQ!6lt3raj|!bUefd?h1w-9B7!cgQ=K$-bR6Lb6J#w^-Vu@%H-lX; zhC9hyQ)^qtqS;*+TsI1qKhoCd(iYSd6)c_H+k5{&NT2z4v+shw9F`vpi_@R^{i~d7 zZ2m86>#J;4?jKQSteUKvZute&PruIBb#&3j6a3R3HqHJ1_+v$Ky2nq$gHjUhpWBMO zrR3N>JK&lvgoFFoH97cRB}6Fm7}RA-}G|jBMNKgw?7c;`SVdyTlrkt=4{V6=lSb? zvkDt9%eyY0fB9ue>eI&WcPx@k1ztArU3&f|-neChFq`2z--J zxA~ZDiOUo9Hys}{!d>gi&&nk&v36~~&G&agy>zwyr@d**ee#|x{V(8N?fNgeSHW$U zM&DDh=^M{*CM*5FhHZ|RhI_Ry=LK`- ztIwG4toi7CN?<|Kr4>K+h8M8!v$!K4U%KZO^U}~;D{pL*Z~t@WO0e>~8SfO9bRRmj z`qozeOP{aRTc!p+O1x<_EpTPoZ#~%`(_-h$DX$8<7jfl(!|?*Q_THkirI%0js|a;6 zPCC57_kQ^+*U#xyJ7snA7Y1xlUolTHVxI-Spe?hVep5{Rg(D7Ed_QW0L_IXl2-449 z8u;$^X`a}N^R0OQ{yuX5r}Z-B6?a+wiX||J89zz)eb1oFz`(`8;OXk;vd$@?2>^Re Bahm`D delta 678 zcmcc4^ND+cCu8A8FA*jM7dLYkS4&4TV`oDrLqk_{3j;$pOA9w+3qwOAMc0%4BXPi^&t29qSEUl*<_yn8ZC@978H@y_s=Z z+oe$Ccx)T1rt`*+63!7DCZssuQ_ZTtM6ZV`E7Ukvwdl9&#o*#x7Yalo%es<{hV9;&f{Q$f#y6NOU-jSiZ9M{ zZpz&2FiGX~kqx}x>hPhMTDUiK+embGr_lCq}I34)FRY8kpl{2 zTd8j8ilA_T`_ir3zB>HLnW!-9#6Jy=O~U`3RbKS|_!%g0#qE#u#0ABVzDMXRlPj70 zm^o1PmSX4GP4x}$*_79-W-7GT%WOV%@6gc}vB#WmG`|{MoBYb7A^O4PiNWvp#D{)# zTAi|HdvAmG(rX(}Fk5&$Ez1)5?4-TE%eE=9V7VFp#vFN;!+Rn;t+s874!gOM^+#4k zWmiqMqa3{V%gCIyGYo@g|4Pnf3)lSk-recKdIn~Opg-&H<~c9=$p8eNu6{1- HoD!Ms+ijp-|zdI@8|Qq-&w>25Ssoj?a62%-ZFI)g#QHK>{lB_`HVl^VxI z1{l#u)p8XkN0np)qgaBbVtfkj>AMgVs-U3v#7fOVqVSZ_wPF<=qycn=!VuS6Z4DNN z{O-mFwKbv)6+#a~G-#??iq|9FaS@E??#~Sw6mf5OA!<2Z6mbfSN>dex67z%k6#NTK zCYSLzJ}_6{0}EgPV0$w`(2MEj?dk1?FPI|$Ai*NXhp=3h7sOz&AvVYYLC~86L2eu$ zmKRq530QbRSc_Ocr3Mo#rO2XPIc|3W%l%UDf)C z&^GjXXl%vhsyjit6{O`di@%r%yDIaNe^4HteC|~I{pr&iUzP8(uKtK#URl<8*p^{O`r7C27*=sFv9UqL zC9XSW`cEb2M7O@3UmRej(P*rXKDBo& z4lMhwxVYa9g6vw#nMW}HPmD`SO7b5+HBM?OLIxf@c+q0!w90&6>(Gt*Rjq)vh3Sx- zoiD8U>AN#<*8s6@ty8nziVFC#wWWK`K=5ZbBErMFF99b+5fSRFgS(z<*HD&txVQRv zmNE*AVuHtXR+CM8eya+-zt*X1$=UuClOFls;$Y$--@4uecWqjljlI1+_xQ|!*GxXh z3~C)5B8= zJxlUkzoOp$z1p$NgA%{=+ndHVN2@BT%mN>kSm~W( zmk6fwwKaWwYpcO4DGhn&OJo0=F)eazzNI?7&b#!p7&C1t%=Pv=OG_nV=5E&Q) zuDp1dWv1Dd<4Pjs=H@0(Uu|T~mibo==Sz0(EOjC7jwdJ+MqadN4~&J6>`nL;ahR~( zQ}aU)$YjD{*TJ62@!jf+YqrP4R900jO-fGoGalJfJ=`)ICwg+BGuCsY&FGD7ne74B z4Hvi9M&$4CczTZgBwFHoKXml&9I^V+7SEzr2VU?k%KDCw0z;iUnwy)Av*$8&U7Mkp z(x`uf6MH<1p2pq(^lH6B)}53aOP1B9+GaQOZ(MWNx4r;eKDyiAqQIi7H!yJ3NaX>G zSuW{U(DD!?Ex>fxM7WA*X~~XDwR*O^Gqk$&_VbR0h6rh%^m-QdvZ7h5 zFZZnXn48qQbk4ujyR;Isoo^SW{tV8r*5CcH_)CuZyFJpevUhOZg}i~_tgj|cy9~p`mV(!{Tg|PH&OMnPi|pEqjc+tRxc>{& z?2|VA!ix6x_5f{j+E71qyJqSX E4?X{J1^@s6 literal 0 HcmV?d00001 diff --git a/res/icon/save-view2.png b/res/icon/save-view2.png deleted file mode 100644 index 298430f78a96a2becdce169fc5343bdb91047149..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2020 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 diff --git a/res/icon/screenshot.png b/res/icon/screenshot.png index d45402ad670d5f467ae52362d47128a86876f5ba..2f2e451f29a54d3bf132e46d586e747a7f3251a1 100644 GIT binary patch delta 833 zcmV-H1HSy%3;zs|NdbVdN)Q4dL^3ruMmRP`HZd?nF)>9qLNPfwLN_uwH8DX&K}Iw} zlX?OiAVe}XH%2%%MK&=oL@_Z%H$pKvI6^lvIW;jsML|Y1LX)-vBnCquGB7bWliUJ2 zBx5&YIW=N6H7#OeGG;AdGc;o@IbvisEoL)fWHLBrVq#=rGLt$3kbka0`jr3x0+mTb zK~#9!>{z{v8bK6)t8PTQ97-%=p@^bl8sSm|u@d|PtRxr^OerM)K!o&(T1J~QV5Qhu z3v$ibMeP=B6$C54zw&0Xds!EEbzSc;!3V?4ym>qO+uywRW@ee~b_A%+`_p(gJeE{V-+B|VB( zee<7TNQg3y)b)Cu_J_maD^go57LGUhfaM347YF8EVENc;weCpA;c(D-dc7XU_m2bg zINM?&*Z+D17~pNbV7b%8kRZ$DQrdsdH|;9scldlh`rLFn?SE+kq`=Vg`j8M!>-G8# zo&-K)^A-!cTCGlf$4@cQcDwz7VVG~&{-uhc`Fvi$>PBvy99uS<4P9CUPjz~|9$1zI zolXb%(tVGy{1)Oho6T=kMOiMF+X0hGrD$C!6et+Nn8{>We8}58TsW?Y@nD9MyjLCYPF{N{r)HX+FJz1o(aejm-NAAvwc5? zp^!Mr_%^!w4EM6o^&0fw+{@(tf(ZAuC;j_zu?#WSKWGQ!w*UhGqzEag;%A}<00000 LNkvXXu0mjfM*DS9 delta 664 zcmey*bDevFCu7Yktec!_VxgOqm~5b%mS$*Znv`msm}G7` z*^YTaz2i+4Uj_!okDe}$Ar-gYL>OitauC@!XX!bq>wI^kmPqa@&3Id1z&*9e`Fcb2 zat_{-;(|9}EjjOGx61Akby-sH^!QHGvG09zj!f|GiBr+7EO=5geb4Vd_kO=C3XHj~ z8foNZl;*x?u|`+ND(REO``RrPgT1y%aw&Z^4FC8i*t7m$LXG)l(<#hd#;xm}C!Eec z|GhSW=XqRi?H|dhuB(>Ct-pWxDeJ95pEvOt{D15Io4VCjU%zl!ETN;kf2X;#5_8(S zJ?}i5ek|Yj{NBImr?R%*N?jRJwPZ@%!Sjoj{H&XH`swo;yLvgk_V`3C;bm)L^lmc* z^>*(RE_FApWsbxo%hrdcQ)Hjv9+)bMEY3)7fWNTsavk$@D0b%Qz(~ye-3{PP-=5H}qapWmytOa3gEI&Ze6=+k$sJ z3K8R6ar^D0vfXF1)IQiqcQdPmrdr(0Fi~2TX=L{2%mV)7OANQHeQ0GY*FSSs6L<0z zsVPzl_6;HNiIrcS`{r&DY1=5gn#asRHpuV*Z=jOcofvkB>e+Yi*=d#Wzp$Pyco*wxC diff --git a/res/icon/select-verts.png b/res/icon/select-verts.png index 5474731d42b9fdcc77d04f87deb569805b6735de..295b377a3754f2cde92541b263ae93041061d8aa 100644 GIT binary patch delta 1418 zcmV;51$FxL5AP6=NdbVdN)Q4bF-12*H#0OeHZe6eF)>9qIWaahK{7BfFhVyuH$jtl z0v;YQMK?k>Gc+?cF*PpE#bTF-&UJZFRu_RLNz zdwn|yO&AGmv)afGIBYranCEycPXb>GmxbnD*z3nsNx;AY@GU<_E37?X2H@>Ke?L1y(AqR>sEn|cmB<~d88 z%GNFAzxG4$&;`yp{SQe1Hz~PR4b%fq>_`3`7gIiX=-8G$v_%uT#bHtK>nJC%6Ct}M39)gVC)i7ML~g`#U$s~^y&n>~?N7XK^`a^h4Zk z@Cl!PbnI;$Fi5=%LZ9*gr1(S5=oxzRhZ}Uj>ZqnSyw??L&F+-qs7;ch+!u-hEl?u(G?(O-qGfY7l5A$=Zj&(6-CYHVzLYSXHinD8|< zHT7dSXI4KX@?m^@{K}>hv9J(oX=(Z12%A~`kjM>>$1|g88l}@|fmA9*s;W{lnG{%D zT#UE3w|io-*gAU$(F$3M=?W)sGUXlDd>tJf zTs-Wy3-EuARFMb!@YIvt-QAu*ARv+$@NZlD zaZmXl92y#$LGI{YulK$ru6V~-B8Lon4ltCIH~T7C9;yX9#_b@)whL&GQV=iI!u~D5 Y0BNDG^Uh)ITmS$707*qoM6N<$f}5a;SpWb4 delta 1234 zcmV;@1TFjT5cCg_NdalGN)Q4bML{+-LqkMFGeSW_F)>9qI6*l?MnObIF)}kSF*lQV z0v;YkK{ho*LqtO}LP0|@F-12xK{-T5K}1F|GBYqSHCBrWHl`} zV>UA_WnnaCEjVN|F)d{|WMelnGdX22He(_nb97Q=W;$e(3j#EgjRQe{*PR`0000Cb zNklO1F0wAWEQ>q)*YC{UO}ER;?t(LkG2Y~x zxpVJ5_kQ=zIcL5R5{U$VdzVT9d&kNLLb-}btK-sGfr#6;z-6G8H$Mk%0>AumAueZ| zc(3+?np0!4Be;zLbsFnE0WM%tmj;-I4=gN}R?Qq{ zEO8u<3qT_#4eM#I=0o44CVM@9ZNKbkJQJ41Vgl8v4ShC@u>jY9^tM+cbcHU;&Z7HD zL*XzBzS&klb!tPOCw;7*_G*Marwbi(G#+PiEXtzMB$PVZV&3{s`Ph2B?e`Y;|G8a6 zZ+kUDb14Tj6%;VB(8NrpB$PVZ3d`o7rG>Gs_G*MEPcTG7d7T18H^hJS8?k5O#Ea zbw|j{{?CgESLXVHmi*ue?mOTraEdp-1bzpur&7NfAxK z|K$gEe0yqY>Zgv5j<5MwzI<0FuJsxtuzup2;NC*Vp&G#bWufsi}$id_LxMI$0nP&?J`^q21`2OhVPw)n5z_4q6); z8pN8K8l{a0IUJ6x#U%gf6P9*>8`VzC{aDRM0>Epk^^*MZ5& z$#d=P?bswc2vWz5&_j{M#l?GZ$c(kMHLX~P0~=l^bnhvn3&(* z-uA4nu4;)7A1y%-D=RDZ=H}+Vc;73d^pG11%>yid5{WQbmKB6?!XO$5g+ekwCm3jL zZ5{Ue{ZVp1_>kQQJw(L^$CVIGY<+#5(c)AGLL6*!b2HlC-#;u#k{{#gBm3AWJ){&w zj}?9-yId|h6(S>pM1aT;!I6=XTSG%b6dV*~kBrnqNEZnc3M{ zsk^&>`yqe%4{mO-0D{MhLjhUP(*OVf07*qoM6N<$g0a3k8UO$Q diff --git a/res/icon/sun.png b/res/icon/sun.png index b13a4814799aa06b92636174009d4a753467cfd2..3e23bd76970437a4d36e00e22b430e8d89ee4450 100644 GIT binary patch delta 1470 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 diff --git a/res/icon/textures.png b/res/icon/textures.png index 83a95020fa4c6d65ee88a0f6633ef18be41b3355..3d5e82139e23d7bbe5015b16adb4b01d7d6fa189 100644 GIT binary patch delta 618 zcmcb^d6jE|Cu7S-FA*jM6DM;s7b90QGh+i&Lqk_{Cktm6XCo&=3uiY&V>cty$#qOJ zFh!;~742eDV=*!?G@pE*$*Mlhz|<_s#3WhQAlbw~H`UU_OxMEL*g`igH967T*uo+; z#nMOY84O7i^6OEJ8bS=yhEeuS|j7?1vQEaktaqG!+L%wDM9#_s&EY{k_1q^%#&YjUrOm%;=VM^yr<^w!yCpHO9nh8DHU_Gna3Ryo-$@My=n-SN4%-ToZYInB1?{I@Hmdv8B9USo5f zJ+1lrCLPxQi!{otcduI2#mcB7`nrCX*3?bEKQPqs|2WvV$Nr6_e(WmE{|wJo2r;sM zPzo`dyg!hk-r{#!dgCsRxvndeR%{YnmJ=!Fm^5n{vzqX{=Yon%yCx(WDlUDXl_KF2 z!FeFXNl4)M#tE1Ay;t^8iz}-YjhTA!u1~=1IY~y2xy4;ak6dJbR%!Q(L8j;IkyLq$ z`gY-F&Sc3XdiL!N opA6OnW5utH`~Q~z5lLY1{}#dg(#`BQ0}yz+viZ5Jb4q9e00dj|cmMzZ delta 432 zcmcc0b%%3;Cu8A8FA*jMQv)YAQ#TV6V<$%=Lqk_{OA9As7YlO>R}&LgCpRaf$#qOJ zFh#~V742eDoBWi?QrFVhz}&*jG)>poz%*GmG1bIS*U})-Sl2kwEHTa8!qC9b%uq?8 zxFjew%_?OwH~?AAhP9-a!K32zi&n6E}G3M zr^vC(QKE#Y-m2mM(_IrMIORQBrl;C*DrN7pv(hFH=QK~yWo^9e?RRRE%4eJA)W`$r zBC?s;O3!bvc#srQ;H7!1UgPv8(dlyy+omumv|JCGyDW{ZPws=5Ljju$L%ql0j%{X- z4jX7x%zNIYwk*>rPSRrbrPC$_-`XD~I6Etb{qayt3bqR-9OWBv|*Nx8lDT5NM_ z7UV{2`^L;;dTmrw&$9a;GXoHKy85}S Ib4q9e07?_AB>(^b diff --git a/res/icon/undo.png b/res/icon/undo.png index 17f80eb2beaeb50e5e5175b010cd6357af78f5eb..4f137f3caf665da05f2efaff59ffa7d1f04838bb 100644 GIT binary patch delta 1184 zcmV;R1Yi5J3x^MoNdbhhN)!ShL^nc2L^U@?HZV6hF)>9qIWR&&LohKzL^DD}MmaV^ zlYRmmAVfDpL_{?=MK&-uI59CrH#smuK|?SxLqsz|L`FF_LzB7!CIm7tF*lRn0y=*% zGB!42Wi~P`FflY^Eo3n?HZ3_YIWsLYFf}(fIAb+pW-vG+Aait5Wo9~LZ)0_BWo~py zL_H#5WN%_+I%IESX=FNZXm50Hb7*gHI%YCAGdW>7I4v_`VP-8cF=aO`VKZZ9EjTt| zI50FeGc`0}WFjvpZrYq9_j9I*MtEhG;VLH+vrdaK**{)~4A8{+`O?(VK{Y;0`0qod<3e&4oQ ztw%PS?ZD&lq`SJhUQJF;-oQK;pk-@hIf{{M0B>7c+XrlnSHTCU0qexX#JJDrOIj=z z!E81QCX?wDXsn^3L1=GpkI&7`g|UCeq$EO#oQN(20)f}COdy~&1NM=Tk=y7Wi^N8w zQ5K>Qr_=dkYHI45k_aUMmj3?!M`Q(ULO=@)4i4VMdrnF$wOURT6Y(9Mh+q$0As45R z1s=HlTQ-~Ja=Bc7AQFkh;_>(lUb&MfRH^DfxQR1gXlZF#<$1n-V`C$na`dFWXgP>q?Ck8clE(l&gLQs>{&G`O z(QX-d9| zDRRXqmS&2S9Ff1Uyu3V%;h!=Bw5e1oC|!#qJe>hW1mu;g)#^NvxQTz%131rji;IiX z=xG$r5?f7)|O*GvlckHPlc9@&k(;T>V8mTF?6n`CZktZQy)l5Cl3U~Z6-n4+Xm zToRO;W|cCTo5^DGL}thOm4|t`85kIEc)B=-RNOi;%~!)EP~`aDgK;UEtG91ZOiff& z^p-ZeCS+vXr6j-f#*dDr{WDoNO%jpzG@H~rH7LSHIx^AF+r{|&|5tr+%Ri^xtNrfx z{>u96w#Co?{=Zv(e&_X~*zG!}HssvybGcDCXMeiRfjeFW$DbUn|9wEQg024k17@2h zlMLRZB}xb86kqTt%1xMjl6y1nq{IR)p$A9bKRp_jAYL-@4#SR=BRgL(ZeO6<(^#qb ztXFY^aKY10fz0O|_O5Dlb&IKq?tUWgAhe}9Zv(@%MSJ%4(#SQT(LPvZ85QlkiykeJnR0%T)Pp0gEBlO8C4X4(Nu5q;6ZAh7Y==N+)78&qol`;+0F0;)w*UYD diff --git a/res/icon/undock.png b/res/icon/undock.png new file mode 100644 index 0000000000000000000000000000000000000000..f169f6932130d6235ce6142ccc83eeb40547d101 GIT binary patch literal 1149 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4fk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+nAI{vB1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxOgGuk*h0bFQqR!T z(!$6@N5ROz&`jUJQs2--*TB%qz|zXVPyq^*fVLH-q*(>IxIyg#@@$ndN=gc>^!3Zj z%k|2Q_413-^$jg8E%gnI^o@*kfhu&1EAvVcD|GXUm0>2hq!uR^WfqiV=I1GZOiWD5 zFD$Tv3bSNU;+l1ennz|zM-B0$V)JVzP|XC=H|jx7ncO3BHWAB;NpiyW)Z+ZoqGVvir744~DzI`cN=+=uFAB-e&w+(vKt_H^esM;Afr7I$DAddqG{Q6U zQu51-HNkp(eXTt6ic1pnl2bihY?Xkf=w)W6Sh+d6IJ!Bynwl9|8WC|IXN4c z8@f817@3-yIKuS0|LG`BKc8dv4z4}1M=z}5`DY9Wgz!U^x!jmqL15f;^dB7B31Wek0&TJF_W{bC; zE{-7;x02=E^Kv>{C*884?*R5gq}jJC3_cG_2G6=fC8%#79XRg+!mA8GKyg!c70$=X6Uh z+?o4f#}9i|w##RZ`dpYK&!RH%P@hB(W5d!pEK`2Aaz@QxRYcV=y3+4+NQMIY0nQine?nV9XJ&u(B)J1EV-$i~2CXTQUfYw{9Mf#B)t K=d#Wzp$P!QczOl^ literal 0 HcmV?d00001 diff --git a/res/icon/vertex-colors.png b/res/icon/vertex-colors.png index 6490d7e0912b6eb5cf3208026e41012253853b41..460a4b1a7be5dbe9ad587279aa954f327665017a 100644 GIT binary patch delta 1444 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 diff --git a/res/icon/view-active.png b/res/icon/view-active.png index fa42156cc5beb39ff36f661459ce947f95f19629..19195d3d5403c6d2a6272d03086d65f281b80df3 100644 GIT binary patch delta 1566 zcmV+(2I2Xx5&jX7NdbVdN)Q4dG%`apK|(??HZe6aF)>9qLNi1;I5|N=GdM6XGD0~w zlX?OiAT%;VG(kc_Fg7tYGBGhlH$pQ+IXF2%LNhopFfu|pH>AUrQ}WM(=(je7SM7rRk0Kj2g_wBX-x2;^~p%0zN$X~2~Q?XG-)(3{sGa44r*dFF&c~yMon87 zW9<+iRcM+C! zyaxUNCV;CmB4YHokdqlyZRwr*9vI5IL%gM&%Be}9s0-W;Y# zF54;W*^0Z z>2&(^-*onDh?2>tn0YK38wcLl{mM?rB!g3_8VG^*U8|m7$y>#F}b_$tL z2p$2K2n1k+{fYz+CfD0b^x%I1!MD_$Ns7<7e!Z3sAJ%AU%4JChOlroiAx+2>ie{Rq zu<$e7YA@B)xEGOm^@`}&F@iHYhr`GS(dElT+qMxkG^A=O98Q4T4

tzrF?f#eD4O zNi)&CV=+?`x!nOoNlgkF_8B%4YbFyJ9{lb2aiXCiwP)TEP=3FgZrp$H8!}jyl364i zH0?{JnF0yxCc|%9TWfHjH$@2h3!7utE~47nrL$LVuJWyMTz(6@VAH0BZzUxfzHO%N zZnJbn1u>T-H6~O+m6df^6rgkGe2WB(jw&DV`%~xd-c=f5@ZZ+KYk3dvwGWoTvREd| zRvbJgp*-Myx_!G5j_QALyz$f&(d4A^3+p&>LJ4H>-*(gS9^UITSk|38jfQN+!IN6; zHSNEZYJ2y|zQg&*m7%GyeRE0C!!_tPI(RUID2k``iM%`&Vb*c+q6&M~Tt`RhH#QgN zCZErlOixeTkjb(YhXkiAK>5@u*naJ>(ojcVYtDo?hEiJu}5t78%C$gDC zlJk&R^yAX2%ou-JlXJ+GD;n+DQ;36=w-+EXyX8mUN!xxfA#3|#60)`*XU=f@@fd$*`d<9znNy`-NrQIMXwa&v z`t{Dv*s>cmURzylHfU|DZO}5cu$4v&TVB2c>Mx^x`+QVYfa7=^0cYja;T@(C_jT zO8*Rc3r{*}mk{#^iH-8F^`ST`&N?fpB(ai*;`-Zu>8JGie^~v`2>m0#03|}pe};OY QSpWb407*qoM6N<$f}>;0C;$Ke delta 1426 zcmV;D1#SBN5v~!CNdalGN)Q4dF)=nYGDAT#GeSZ%F)>9qIYC7?MnpL^H8MFeMnyI- zlX?OiATcpEG%`a$GBZL#G%+znH#tE?H%3G`H8nChGDbxCK?{l7_=(}fmaWZx}ZL!x-kUrecFF*euzGh9-*) z;#LX-KA_vT57Vt%?n-frV}eJjs`#Yu-{&asm*8iNVGe65HrRqq*e=~>FFe%4d{La4 zN%2waDmEu3CN{l))7XfpD1m2gxm-@H*-eAZ;`HLU!hPSP16MEt8Ba0lzVVRezW)Yf2|D-WuolrSS&_sYiq<_5Ff&3 z=2928DHIBoI#;i93gsLc;6#4ynivWOgJO+Y48UI46MMsd25iBmePCc18lVN&zKI{+ zW_obllYM=MR<2xeBNps-y8zGd7C^&>Q&Ur%uscg}WNLg|oS?_4t*wn}YfIl79s!uk zWHJIgH=H^ZcRqZGYyTZH%FN)}7v+@$RK2jEYj(Ro+0jvjP2f^s_7a5QT0>NxGcgQf z&C=2mzcuE6MH;{u)?*%Cfe7R+fPUTe>nZQx;E2paKge7ZeNkfU0xRdv9S)y3;Z*rm z0WScX%_cy6p=GmKibkWvS6uX+PA7Rh9w8PDM+89t9swFZ`vwP-4d>5)D-+`*b7C~V z%TV9=q^J);qOb3x<A=mIQJo2Li zV#2a+s`zq!Usu(4BK`gC;Uh4OJt3X6gWvmoOJ;)u+G#8I;t$K%I&-<>;Q@4b6Z z6iu7vr4!{|u6rs!G><0`>*)z(T3cP2mKHVJ)MVA%ZcEYO5Q;46w64bE#hPHSV1N2l zv(L?cCF({-LTWS``YYwfP6b*(7HKHZUSr%_3N$01XdESeg>IKShuNsH7Z7j^r%@-UY4px#b}hV@r@F8hx&RizsvrWB7gsfV&!Al|98y3 gp#PbmKLi*6p*4al2-gbZ00000NkvXXu0mh|g6&kCF8}}l diff --git a/res/nifskope.qrc b/res/nifskope.qrc index e4153cfe6..c4a46d69d 100644 --- a/res/nifskope.qrc +++ b/res/nifskope.qrc @@ -9,10 +9,12 @@ icon/img_flag.png img/havok_logo.png img/qhull_cone.gif + + icon/handle.png - icon/collapse.png - icon/expand.png icon/sizegrip.png + icon/close.png + icon/undock.png icon/button_view_walk.png @@ -30,9 +32,8 @@ icon/screenshot.png icon/save.png icon/load.png - icon/save-view2.png - icon/load-view2.png - icon/load-dark.png + icon/save-view.png + icon/load-view.png icon/bulb.png icon/bulb-off.png icon/textures.png @@ -58,5 +59,7 @@ icon/select-object.png icon/select-verts.png icon/skinned.png + icon/collapse.png + icon/expand.png diff --git a/src/nifskope_ui.cpp b/src/nifskope_ui.cpp index 9ac4b56d8..2c374798d 100644 --- a/src/nifskope_ui.cpp +++ b/src/nifskope_ui.cpp @@ -795,7 +795,7 @@ QWidget * NifSkope::filePathWidget( QWidget * parent ) // Navigate to Filepath auto navigateToFilepath = new QPushButton( "", filepathWidget ); navigateToFilepath->setFlat( true ); - navigateToFilepath->setIcon( QIcon( ":btn/loadDark" ) ); + navigateToFilepath->setIcon( QIcon( ":btn/load" ) ); navigateToFilepath->setIconSize( QSize( 16, 16 ) ); navigateToFilepath->setStatusTip( tr( "Show in Explorer" ) ); diff --git a/src/ui/nifskope.ui b/src/ui/nifskope.ui index 2b85387c4..296d053f2 100644 --- a/src/ui/nifskope.ui +++ b/src/ui/nifskope.ui @@ -534,7 +534,7 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 - :/img/expand:/img/expand + :/btn/expand:/btn/expand @@ -563,7 +563,7 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 - :/img/collapse:/img/collapse + :/btn/collapse:/btn/collapse @@ -680,7 +680,7 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 - :/img/expand:/img/expand + :/btn/expand:/btn/expand @@ -709,7 +709,7 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 - :/img/collapse:/img/collapse + :/btn/collapse:/btn/collapse @@ -1156,8 +1156,8 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 - :/btn/loadView2 - :/btn/userViewActive:/btn/loadView2 + :/btn/loadView + :/btn/userViewActive:/btn/loadView Load View @@ -1236,7 +1236,7 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 - :/btn/saveView2:/btn/saveView2 + :/btn/saveView:/btn/saveView Save View @@ -1688,9 +1688,8 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 true - - :/btn/textures - + + :/btn/textures:/btn/textures Textures From dfb4993cc10832959fd07cc0b2d7423a562574bd Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 11 Jul 2017 12:32:34 -0400 Subject: [PATCH 053/152] [UI] Emit signal for ColorLineEdit --- src/ui/settingspane.cpp | 1 + src/ui/widgets/colorwheel.cpp | 2 ++ src/ui/widgets/colorwheel.h | 3 +++ 3 files changed, 6 insertions(+) diff --git a/src/ui/settingspane.cpp b/src/ui/settingspane.cpp index 7076ecf87..d92f9cf3d 100644 --- a/src/ui/settingspane.cpp +++ b/src/ui/settingspane.cpp @@ -361,6 +361,7 @@ SettingsRender::SettingsRender( QWidget * parent ) : w->setColor( color ); connect( w, &ColorWheel::sigColorEdited, this, &SettingsPane::modifyPane ); + connect( e, &ColorLineEdit::textEdited, this, &SettingsPane::modifyPane ); }; color( "Background", ui->colorBackground, ui->background, QColor( 0, 0, 0 ) ); diff --git a/src/ui/widgets/colorwheel.cpp b/src/ui/widgets/colorwheel.cpp index 9f4cef2b6..4c0e6281b 100644 --- a/src/ui/widgets/colorwheel.cpp +++ b/src/ui/widgets/colorwheel.cpp @@ -562,6 +562,8 @@ void ColorLineEdit::setWheel( ColorWheel * cw, const QString & str ) QColor wc = wheel->getColor(); if ( c.toRgb() != wc.toRgb() ) wheel->setColor( c ); + + emit textEdited( colorTxt ); } ); } diff --git a/src/ui/widgets/colorwheel.h b/src/ui/widgets/colorwheel.h index c0b330e0e..3c45019e8 100644 --- a/src/ui/widgets/colorwheel.h +++ b/src/ui/widgets/colorwheel.h @@ -122,6 +122,9 @@ class ColorLineEdit final : public QWidget void setTitle( const QString & ); void setColor( const QColor & ); +signals: + void textEdited( const QString & ); + public slots: void setAlpha( float ); From a55da49caf40374998d62eff97e4ab45d9e9ad89 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 18 Jul 2017 19:46:54 -0400 Subject: [PATCH 054/152] [UI] New UI Theming Completely new dark/light theme using Qt Fusion style with palette swapping. When using dark/light the palette is customizable with 6 colors in Settings. The toolbar size is also customizable. Split lighting widget into own UI class. --- NifSkope.pro | 5 +- res/style.qss | 97 +++--- src/main.cpp | 26 -- src/model/nifdelegate.cpp | 7 +- src/nifskope.cpp | 5 +- src/nifskope.h | 31 +- src/nifskope_ui.cpp | 469 ++++++++++++++++++------------ src/ui/about_dialog.cpp | 3 + src/ui/nifskope.ui | 95 +++--- src/ui/settingsdialog.cpp | 7 + src/ui/settingsdialog.h | 1 + src/ui/settingsdialog.ui | 28 -- src/ui/settingsgeneral.ui | 109 +++++++ src/ui/settingspane.cpp | 25 +- src/ui/settingsrender.ui | 8 +- src/ui/settingsresources.ui | 24 -- src/ui/widgets/floatslider.cpp | 14 +- src/ui/widgets/lightingwidget.cpp | 73 +++++ src/ui/widgets/lightingwidget.h | 50 ++++ src/ui/widgets/lightingwidget.ui | 367 +++++++++++++++++++++++ 20 files changed, 1072 insertions(+), 372 deletions(-) create mode 100644 src/ui/widgets/lightingwidget.cpp create mode 100644 src/ui/widgets/lightingwidget.h create mode 100644 src/ui/widgets/lightingwidget.ui diff --git a/NifSkope.pro b/NifSkope.pro index c6c8856da..0c45d95a8 100644 --- a/NifSkope.pro +++ b/NifSkope.pro @@ -190,6 +190,7 @@ HEADERS += \ src/ui/widgets/floatslider.h \ src/ui/widgets/groupbox.h \ src/ui/widgets/inspect.h \ + src/ui/widgets/lightingwidget.h \ src/ui/widgets/nifcheckboxlist.h \ src/ui/widgets/nifeditors.h \ src/ui/widgets/nifview.h \ @@ -276,6 +277,7 @@ SOURCES += \ src/ui/widgets/floatslider.cpp \ src/ui/widgets/groupbox.cpp \ src/ui/widgets/inspect.cpp \ + src/ui/widgets/lightingwidget.cpp \ src/ui/widgets/nifcheckboxlist.cpp \ src/ui/widgets/nifeditors.cpp \ src/ui/widgets/nifview.cpp \ @@ -309,7 +311,8 @@ FORMS += \ src/ui/settingsdialog.ui \ src/ui/settingsgeneral.ui \ src/ui/settingsrender.ui \ - src/ui/settingsresources.ui + src/ui/settingsresources.ui \ + src/ui/widgets/lightingwidget.ui ############################### diff --git a/res/style.qss b/res/style.qss index e4d10b0fe..56a43148d 100644 --- a/res/style.qss +++ b/res/style.qss @@ -1,63 +1,62 @@ -/* - Stylish tooltips +/* Variables: + Qt Colors: + palette(base) + palette(alternate-base) + palette(text) + palette(highlight) + palette(highlighted-text) + palette(bright-text) + + Template Variables: + ${theme} = Theme name ("dark", "light") for icon path customization + ${rgb} = Highlight colors in an "R, G, B" string to combine with opacity in rgba() */ -QToolTip -{ - opacity: 223; - color: steelblue; - border: 1px solid steelblue; - border-radius: 5px; - padding: 4px; - background: ghostwhite; - font-size: 10px; -} -/* - Make the disabled rows of the table views more distinguishable -*/ +/* Remove light border for dark theme */ +QToolBar { border: 1px solid transparent; } -QTreeView -{ - background: white; - alternate-background-color: #F1F5FA; - selection-background-color: #3D80DF; - selection-color: white; - color: black; -} +/* Fix tooltip display */ +QToolTip { color: #ffffff; background-color: #555; border: 1px solid transparent; } -QTreeView:disabled -{ - background: #C0C0C0; - alternate-background-color: #C0C0C0; - color: #7D7D7D; +/* Fix display of Settings category list */ +QListWidget#categoryList { padding: 10px; } +QListWidget#categoryList::item { height: 20px; padding: 10px; } +QListWidget#categoryList::item::text { padding-left: 10px; } + +/* Fix display of close/undock for dark themes */ +QDockWidget { + titlebar-close-icon: url(:/wnd/close); + titlebar-normal-icon: url(:/wnd/undock); } +/* Toolbar Buttons */ -/* - Give the fileselectors a green tint on success and a red one on error -*/ +/* Flyout menus need padding for down arrow */ +#btnFlyoutMenu { padding-left: 2px; padding-right: 10px; } -FileSelector[state="0"] > QLineEdit -{ - background-color: auto; +/* Fix toolbar button spacing */ +#tRender QToolButton#btnRender:enabled, +#tAnim QToolButton:enabled, +#mLight QAbstractButton { + padding: 1px 1px; + margin: 0px 0px 0px 1px; + border-radius: 2px; + border: 1px solid transparent; } -FileSelector[state="1"] > QLineEdit -{ - background-color: #cfc; +/* Selected toolbar buttons */ +#tRender QToolButton#btnRender:enabled:checked, +#tAnim QToolButton:enabled:checked, +#mLight QAbstractButton:enabled:checked { + border: 1px solid rgba(0, 0, 0, 32); + background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.66, fx:0.5, fy:0.5, stop:0 rgba(255, 255, 255, 0), stop:0.5 rgba(${rgb}, 32), stop:1 rgba(${rgb}, 128)); } -FileSelector[state="2"] > QLineEdit -{ - background-color: #fcc; -} - - -/* - Add a border to the FloatSliderEditBoxes -*/ -FloatSliderEditBox -{ - border: 1px solid black; +/* Hovered toolbar buttons */ +#tRender QToolButton#btnRender:enabled:hover, +#tAnim QToolButton:enabled:hover, +#mLight QAbstractButton:enabled:hover { + border: 1px solid rgba(${rgb}, 255); + background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.66, fx:0.5, fy:0.5, stop:0 rgba(255, 255, 255, 0), stop:0.33 rgba(${rgb}, 64), stop:1 rgba(${rgb}, 128)); } diff --git a/src/main.cpp b/src/main.cpp index 274acc2fa..0a351c5e3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -87,32 +87,6 @@ int main( int argc, char * argv[] ) qRegisterMetaType( "NifValue" ); QMetaType::registerComparators(); - // Find stylesheet - QDir qssDir( QApplication::applicationDirPath() ); - QStringList qssList( QStringList() - << "style.qss" -#ifdef Q_OS_LINUX - << "/usr/share/nifskope/style.qss" -#endif - ); - QString qssName; - for ( const QString& str : qssList ) { - if ( qssDir.exists( str ) ) { - qssName = qssDir.filePath( str ); - break; - } - } - - // Load stylesheet - if ( !qssName.isEmpty() ) { - QFile style( qssName ); - - if ( style.open( QFile::ReadOnly ) ) { - a->setStyleSheet( style.readAll() ); - style.close(); - } - } - // Set locale QSettings cfg( QString( "%1/nifskope.ini" ).arg( QCoreApplication::applicationDirPath() ), QSettings::IniFormat ); cfg.beginGroup( "Settings" ); diff --git a/src/model/nifdelegate.cpp b/src/model/nifdelegate.cpp index e70ab0d24..8be578c85 100644 --- a/src/model/nifdelegate.cpp +++ b/src/model/nifdelegate.cpp @@ -191,8 +191,11 @@ class NifDelegate final : public QItemDelegate QSize sizeHint( const QStyleOptionViewItem & option, const QModelIndex & index ) const override final { QString text = index.data( NifSkopeDisplayRole ).toString(); - QRect textRect( 0, 0, option.fontMetrics.width( text ), option.fontMetrics.lineSpacing() * (text.count( QLatin1Char( '\n' ) ) + 1) ); - return textRect.size(); + auto height = option.fontMetrics.lineSpacing() * (text.count( QLatin1Char( '\n' ) ) + 1); + // Increase height by 25% + height *= 1.25; + + return {option.fontMetrics.width( text ), height}; } QWidget * createEditor( QWidget * parent, const QStyleOptionViewItem &, const QModelIndex & index ) const override final diff --git a/src/nifskope.cpp b/src/nifskope.cpp index a5832c140..02c5dc4af 100644 --- a/src/nifskope.cpp +++ b/src/nifskope.cpp @@ -144,7 +144,7 @@ NifSkope::NifSkope() qApp->installEventFilter( this ); // Init Dialogs - aboutDialog = new AboutDialog( this ); + if ( !options ) options = new SettingsDialog; @@ -192,6 +192,7 @@ NifSkope::NifSkope() list->setSortingEnabled( false ); list->setItemDelegate( nif->createDelegate( this, book ) ); list->installEventFilter( this ); + list->header()->resizeSection( NifModel::NameCol, 250 ); // Block Details tree = ui->tree; @@ -200,6 +201,8 @@ NifSkope::NifSkope() tree->setItemDelegate( nif->createDelegate( this, book ) ); tree->installEventFilter( this ); tree->header()->moveSection( 1, 2 ); + tree->header()->resizeSection( NifModel::NameCol, 140 ); + tree->header()->resizeSection( NifModel::ValueCol, 250 ); // Allow multi-row paste // Note: this has some side effects such as vertex selection // in viewport being wrong if you attempt to select many rows. diff --git a/src/nifskope.h b/src/nifskope.h index d7e98c02c..e783f8831 100644 --- a/src/nifskope.h +++ b/src/nifskope.h @@ -38,6 +38,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include #include @@ -82,6 +83,13 @@ class QTimer; class QTreeView; class QUdpSocket; +namespace nstheme +{ + enum WindowColor { Base, BaseAlt, Text, Highlight, HighlightText, BrightText }; + enum WindowTheme { ThemeDark, ThemeLight, ThemeWindows, ThemeWindowsXP }; + enum ToolbarSize { ToolbarSmall, ToolbarLarge }; +} + //! @file nifskope.h NifSkope, IPCsocket @@ -144,6 +152,11 @@ class NifSkope final : public QMainWindow enum { NumRecentFiles = 10 }; + static QColor defaultsDark[6]; + static QColor defaultsLight[6]; + + static void reloadTheme(); + signals: void beginLoading(); void completeLoading( bool, QString & ); @@ -201,6 +214,8 @@ public slots: void on_aSettings_triggered(); + void on_mTheme_triggered( QAction * action ); + protected slots: void openDlg(); @@ -285,11 +300,23 @@ protected slots: void setViewFont( const QFont & ); + //! Load the theme + void loadTheme(); + //! Sync the theme actions in the UI + void setThemeActions(); + //! Set the toolbar size + void setToolbarSize(); + //! Set the theme + void setTheme( nstheme::WindowTheme theme ); + //! Migrate settings from older versions of NifSkope. void migrateSettings() const; - //! "About NifSkope" dialog. - QWidget * aboutDialog; + //! All QActions in the UI + QSet allActions; + + nstheme::WindowTheme theme = nstheme::ThemeDark; + nstheme::ToolbarSize toolbarSize = nstheme::ToolbarLarge; QString currentFile; BSA * currentArchive = nullptr; diff --git a/src/nifskope_ui.cpp b/src/nifskope_ui.cpp index 2c374798d..8f7abca42 100644 --- a/src/nifskope_ui.cpp +++ b/src/nifskope_ui.cpp @@ -44,6 +44,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "ui/widgets/fileselect.h" #include "ui/widgets/floatslider.h" #include "ui/widgets/floatedit.h" +#include "ui/widgets/lightingwidget.h" #include "ui/widgets/nifview.h" #include "ui/widgets/refrbrowser.h" #include "ui/widgets/inspect.h" @@ -75,6 +76,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include QString nstypes::operator""_uip( const char * str, size_t ) { @@ -90,6 +92,27 @@ QString nstypes::operator""_uip( const char * str, size_t ) } using namespace nstypes; +using namespace nstheme; + + +QColor NifSkope::defaultsDark[6] = { + QColor( 60, 60, 60 ), /// nstheme::Base + QColor( 50, 50, 50 ), /// nstheme::BaseAlt + Qt::white, /// nstheme::Text + QColor( 204, 204, 204 ), /// nstheme::Highlight + Qt::black, /// nstheme::HighlightText + QColor( 255, 66, 58 ) /// nstheme::BrightText +}; + +QColor NifSkope::defaultsLight[6] = { + QColor( 245, 245, 245 ), /// nstheme::Base + QColor( 255, 255, 255 ), /// nstheme::BaseAlt + Qt::black, /// nstheme::Text + QColor( 42, 130, 218 ), /// nstheme::Highlight + Qt::white, /// nstheme::HighlightText + Qt::red /// nstheme::BrightText +}; + //! @file nifskope_ui.cpp UI logic for %NifSkope's main window. @@ -99,28 +122,10 @@ NifSkope * NifSkope::createWindow( const QString & fname ) NifSkope * skope = new NifSkope; skope->setAttribute( Qt::WA_DeleteOnClose ); skope->restoreUi(); + skope->loadTheme(); skope->show(); skope->raise(); - // Example Dark style - //QApplication::setStyle( QStyleFactory::create( "Fusion" ) ); - //QPalette darkPalette; - //darkPalette.setColor( QPalette::Window, QColor( 53, 53, 53 ) ); - //darkPalette.setColor( QPalette::WindowText, Qt::white ); - //darkPalette.setColor( QPalette::Base, QColor( 25, 25, 25 ) ); - //darkPalette.setColor( QPalette::AlternateBase, QColor( 53, 53, 53 ) ); - //darkPalette.setColor( QPalette::ToolTipBase, Qt::white ); - //darkPalette.setColor( QPalette::ToolTipText, Qt::white ); - //darkPalette.setColor( QPalette::Text, Qt::white ); - //darkPalette.setColor( QPalette::Button, QColor( 53, 53, 53 ) ); - //darkPalette.setColor( QPalette::ButtonText, Qt::white ); - //darkPalette.setColor( QPalette::BrightText, Qt::red ); - //darkPalette.setColor( QPalette::Link, QColor( 42, 130, 218 ) ); - //darkPalette.setColor( QPalette::Highlight, QColor( 42, 130, 218 ) ); - //darkPalette.setColor( QPalette::HighlightedText, Qt::black ); - //qApp->setPalette( darkPalette ); - //qApp->setStyleSheet( "QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }" ); - if ( !fname.isEmpty() ) { skope->loadFile( fname ); } @@ -137,13 +142,25 @@ void NifSkope::initActions() aRCondition = ui->aRCondition; aSelectFont = ui->aSelectFont; + // Build all actions list + allActions = QSet::fromList( + ui->tFile->actions() + << ui->mRender->actions() + << ui->tRender->actions() + << ui->tAnim->actions() + ); + // Undo/Redo undoAction = nif->undoStack->createUndoAction( this, tr( "&Undo" ) ); undoAction->setShortcut( QKeySequence::Undo ); + undoAction->setObjectName( "aUndo" ); undoAction->setIcon( QIcon( ":btn/undo" ) ); + allActions << undoAction; redoAction = nif->undoStack->createRedoAction( this, tr( "&Redo" ) ); redoAction->setShortcut( QKeySequence::Redo ); + redoAction->setObjectName( "aRedo" ); redoAction->setIcon( QIcon( ":btn/redo" ) ); + allActions << redoAction; // TODO: Back/Forward button in Block List //idxForwardAction = indexStack->createRedoAction( this ); @@ -274,7 +291,10 @@ void NifSkope::initActions() connect( aCondition, &QAction::toggled, tree, &NifTreeView::setRowHiding ); connect( aCondition, &QAction::toggled, kfmtree, &NifTreeView::setRowHiding ); - connect( ui->aAboutNifSkope, &QAction::triggered, aboutDialog, &AboutDialog::show ); + connect( ui->aAboutNifSkope, &QAction::triggered, []() { + auto aboutDialog = new AboutDialog(); + aboutDialog->show(); + } ); connect( ui->aAboutQt, &QAction::triggered, qApp, &QApplication::aboutQt ); connect( ui->aPrintView, &QAction::triggered, ogl, &GLView::saveImage ); @@ -428,19 +448,18 @@ void NifSkope::initMenu() for ( int i = 0; i < NumRecentFiles; ++i ) mOpen->addAction( recentFileActs[i] ); + auto setFlyout = []( QToolButton * btn, QMenu * m ) { + btn->setObjectName( "btnFlyoutMenu" ); + btn->setMenu( m ); + btn->setPopupMode( QToolButton::InstantPopup ); + }; + // Append Menu to tFile actions for ( auto child : ui->tFile->findChildren() ) { - if ( child->defaultAction() == ui->aSaveMenu ) { - child->setMenu( mSave ); - child->setPopupMode( QToolButton::InstantPopup ); - child->setStyleSheet( "padding-left: 2px; padding-right: 10px;" ); - } - - if ( child->defaultAction() == ui->aOpenMenu ) { - child->setMenu( mOpen ); - child->setPopupMode( QToolButton::InstantPopup ); - child->setStyleSheet( "padding-left: 2px; padding-right: 10px;" ); + setFlyout( child, mSave ); + } else if ( child->defaultAction() == ui->aOpenMenu ) { + setFlyout( child, mOpen ); } } @@ -449,38 +468,45 @@ void NifSkope::initMenu() //updateRecentArchiveFileActions(); // Lighting Menu - // TODO: Split off into own widget auto mLight = lightingWidget(); - // Append Menu to tFile actions + // Append Menu to tRender actions for ( auto child : ui->tRender->findChildren() ) { if ( child->defaultAction() == ui->aLightMenu ) { - child->setMenu( mLight ); - child->setPopupMode( QToolButton::InstantPopup ); - child->setStyleSheet( "padding-left: 2px; padding-right: 10px;" ); + setFlyout( child, mLight ); + } else { + child->setObjectName( "btnRender" ); } } // BSA Recent Archives auto tRecentArchives = new QToolButton( this ); - tRecentArchives->setObjectName( "tRecentArchives" ); tRecentArchives->setText( "Recent Archives" ); - tRecentArchives->setStyleSheet( "padding-right: 10px;" ); - tRecentArchives->setMenu( ui->mRecentArchives ); - tRecentArchives->setPopupMode( QToolButton::InstantPopup ); + setFlyout( tRecentArchives, ui->mRecentArchives ); // BSA Recent Files auto tRecentArchiveFiles = new QToolButton( this ); - tRecentArchiveFiles->setObjectName( "tRecentArchiveFiles" ); tRecentArchiveFiles->setText( "Recent Files" ); - tRecentArchiveFiles->setStyleSheet( "padding-right: 10px;" ); - tRecentArchiveFiles->setMenu( mRecentArchiveFiles ); - tRecentArchiveFiles->setPopupMode( QToolButton::InstantPopup ); + setFlyout( tRecentArchiveFiles, mRecentArchiveFiles ); ui->bsaTitleBar->layout()->addWidget( tRecentArchives ); ui->bsaTitleBar->layout()->addWidget( tRecentArchiveFiles ); + + + // Theme Menu + + QActionGroup * grpTheme = new QActionGroup( this ); + + // Fill the action data with the integer correlating to + // their position in WindowTheme and add to the action group. + int i = 0; + auto themes = ui->mTheme->actions(); + for ( auto a : themes ) { + a->setData( i++ ); + grpTheme->addAction( a ); + } } @@ -621,159 +647,17 @@ QMenu * NifSkope::lightingWidget() { QMenu * mLight = new QMenu( this ); mLight->setObjectName( "mLight" ); - - mLight->setStyleSheet( - R"qss(#mLight { background: #f5f5f5; padding: 8px; border: 1px solid #CCC; })qss" - ); - - auto onOffWidget = new QWidget; - onOffWidget->setContentsMargins( 0, 0, 0, 0 ); - auto onOffLayout = new QHBoxLayout; - onOffLayout->setContentsMargins( 0, 0, 0, 0 ); - onOffWidget->setLayout( onOffLayout ); - - auto chkLighting = new QToolButton( mLight ); - chkLighting->setObjectName( "chkLighting" ); - chkLighting->setCheckable( true ); - chkLighting->setChecked( true ); - chkLighting->setDefaultAction( ui->aLighting ); - chkLighting->setIconSize( QSize( 18, 18 ) ); - chkLighting->setToolButtonStyle( Qt::ToolButtonTextBesideIcon ); - //chkLighting->setText( " " + ui->aLighting->text() ); // Resets during toggle - //chkLighting->setStatusTip( ui->aLighting->statusTip() ); Doesn't work - chkLighting->setStyleSheet( R"qss( - #chkLighting { padding: 5px; } - )qss" ); - - auto lightingOptionsWidget = new QWidgetAction( mLight ); - - auto optionsWidget = new QWidget; - optionsWidget->setContentsMargins( 0, 0, 0, 0 ); - auto optionsLayout = new QHBoxLayout; - optionsLayout->setContentsMargins( 0, 0, 0, 0 ); - optionsWidget->setLayout( optionsLayout ); - - - auto chkFrontal = new QToolButton( mLight ); - chkFrontal->setObjectName( "chkFrontal" ); - chkFrontal->setCheckable( true ); - chkFrontal->setChecked( true ); - chkFrontal->setDefaultAction( ui->aFrontalLight ); - //chkFrontal->setIconSize( QSize( 16, 16 ) ); - //chkFrontal->setToolButtonStyle( Qt::ToolButtonTextBesideIcon ); - //chkFrontal->setStatusTip( ui->aFrontalLight->statusTip() ); Doesn't work - chkFrontal->setStyleSheet( R"qss( #chkFrontal { padding: 5px; } )qss" ); - - onOffLayout->addWidget( chkLighting ); - onOffLayout->addWidget( chkFrontal ); - - // Slider lambda - auto sld = [this]( QWidget * parent, const QString & img, int min, int max, int val ) { - - auto slider = new QSlider( Qt::Horizontal, parent ); - slider->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Maximum ); - slider->setRange( min, max ); - slider->setSingleStep( max / 4 ); - slider->setTickInterval( max / 2 ); - slider->setTickPosition( QSlider::TicksBelow ); - slider->setValue( val ); - slider->setStyleSheet( "background: transparent url(" + img + ");" + R"qss( - background-repeat: no-repeat; - background-position: left; - background-origin: margin; - background-clip: margin; - margin-left: 20px; - )qss" - ); - - return slider; - }; - - // Lighting position, uses -720 to 720 and GLView divides it by 4 - // because QSlider uses integers and we'd like less choppy motion - auto sldDeclination = sld( chkFrontal, ":/btn/lightVertical", -720, 720, 0 ); - auto sldPlanarAngle = sld( chkFrontal, ":/btn/lightHorizontal", -720, 720, 0 ); - auto sldBrightness = sld( chkFrontal, ":/btn/sun", 0, 1440, 720 ); - auto sldAmbient = sld( chkFrontal, ":/btn/cloud", 0, 1440, 540 ); - - sldDeclination->setDisabled( true ); - sldPlanarAngle->setDisabled( true ); - - // Button lambda - auto btn = [this]( QAction * act, const QString & name, QMenu * menu ) { - auto button = new QToolButton( menu ); - button->setObjectName( name ); - button->setCheckable( true ); - button->setChecked( true ); - button->setDefaultAction( act ); - button->setIconSize( QSize( 16, 16 ) ); - //button->setStatusTip( ui->aTextures->statusTip() ); Doesn't work - button->setStyleSheet( R"qss( padding: 3px 2px 2px 3px; )qss" ); - - return button; - }; - - auto chkTextures = btn( ui->aTextures, "chkTextures", mLight ); - auto chkVertexColors = btn( ui->aVertexColors, "chkVertexColors", mLight ); - auto chkNormalsOnly = btn( ui->aVisNormals, "chkNormalsOnly", mLight ); - - optionsLayout->addWidget( chkTextures ); - optionsLayout->addWidget( chkVertexColors ); - optionsLayout->addWidget( chkNormalsOnly ); - - lightingOptionsWidget->setDefaultWidget( optionsWidget ); - - auto lightingGroup = new QGroupBox( mLight ); - lightingGroup->setObjectName( "lightingGroup" ); - lightingGroup->setContentsMargins( 0, 0, 0, 0 ); - lightingGroup->setStyleSheet( R"qss( #lightingGroup { padding: 0; border: none; } )qss" ); - //lightingGroup->setDisabled( true ); - - auto lightingGroupVbox = new QVBoxLayout; - lightingGroupVbox->setContentsMargins( 0, 0, 0, 0 ); - lightingGroupVbox->setSpacing( 0 ); - lightingGroup->setLayout( lightingGroupVbox ); - - lightingGroupVbox->addWidget( sldBrightness ); - lightingGroupVbox->addWidget( sldAmbient ); - lightingGroupVbox->addWidget( sldDeclination ); - lightingGroupVbox->addWidget( sldPlanarAngle ); - - // Disable lighting sliders when Frontal - connect( chkFrontal, &QToolButton::toggled, sldDeclination, &QSlider::setDisabled ); - connect( chkFrontal, &QToolButton::toggled, sldPlanarAngle, &QSlider::setDisabled ); - - // Disable Frontal checkbox (and sliders) when no lighting - connect( chkLighting, &QToolButton::toggled, chkFrontal, &QToolButton::setEnabled ); - connect( chkLighting, &QToolButton::toggled, ui->aVisNormals, &QAction::setEnabled ); - connect( chkLighting, &QToolButton::toggled, [sldDeclination, sldPlanarAngle, chkFrontal]( bool checked ) { - if ( !chkFrontal->isChecked() ) { - // Don't enable the sliders if Frontal is checked - sldDeclination->setEnabled( checked ); - sldDeclination->setEnabled( checked ); - } - } ); - - // Inform ogl of changes - //connect( chkLighting, &QCheckBox::toggled, ogl, &GLView::lightingToggled ); - connect( sldBrightness, &QSlider::valueChanged, ogl, &GLView::setBrightness ); - connect( sldAmbient, &QSlider::valueChanged, ogl, &GLView::setAmbient ); - connect( sldDeclination, &QSlider::valueChanged, ogl, &GLView::setDeclination ); - connect( sldPlanarAngle, &QSlider::valueChanged, ogl, &GLView::setPlanarAngle ); - connect( chkFrontal, &QToolButton::toggled, ogl, &GLView::setFrontalLight ); - - // Set up QWidgetActions so they can be added to a QMenu - auto lightingWidget = new QWidgetAction( mLight ); - lightingWidget->setDefaultWidget( onOffWidget ); + auto lightingWidget = new LightingWidget( ogl, mLight ); + lightingWidget->setActions( {ui->aLighting, ui->aTextures, ui->aVertexColors, + ui->aSpecular, ui->aCubeMapping, ui->aGlow, + ui->aVisNormals, ui->aSilhouette} ); + auto aLightingWidget = new QWidgetAction( mLight ); + aLightingWidget->setDefaultWidget( lightingWidget ); - auto lightingAngleWidget = new QWidgetAction( mLight ); - lightingAngleWidget->setDefaultWidget( lightingGroup ); - mLight->addAction( lightingWidget ); - mLight->addAction( lightingAngleWidget ); - mLight->addAction( lightingOptionsWidget ); + mLight->addAction( aLightingWidget ); return mLight; } @@ -1005,6 +889,8 @@ void NifSkope::saveUi() const settings.setValue( "Window State"_uip, saveState( 0x073 ) ); settings.setValue( "Window Geometry"_uip, saveGeometry() ); + settings.setValue( "Theme", theme ); + settings.setValue( "File/Auto Sanitize", aSanitize->isChecked() ); settings.setValue( "List Mode"_uip, (gListMode->checkedAction() == aList ? "list" : "hierarchy") ); @@ -1098,6 +984,198 @@ void NifSkope::setViewFont( const QFont & font ) ogl->setFont( font ); } +void NifSkope::reloadTheme() +{ + for ( QWidget * widget : QApplication::topLevelWidgets() ) { + NifSkope * win = qobject_cast(widget); + if ( win ) { + win->loadTheme(); + } + } +} + +void NifSkope::loadTheme() +{ + QSettings settings; + theme = WindowTheme( settings.value( "Theme", ThemeDark ).toInt() ); + ui->mTheme->actions()[theme]->setChecked( true ); + + toolbarSize = ToolbarSize( settings.value( "Settings/Theme/Large Icons", ToolbarLarge ).toBool() ); + + //setThemeActions(); + setToolbarSize(); + + switch ( theme ) + { + case ThemeWindowsXP: + QApplication::setStyle( QStyleFactory::create( "WindowsXP" ) ); + qApp->setStyleSheet(""); + qApp->setPalette( style()->standardPalette() ); + return; + case ThemeWindows: + QApplication::setStyle( QStyleFactory::create( "WindowsVista" ) ); + qApp->setStyleSheet(""); + qApp->setPalette( style()->standardPalette() ); + return; + case ThemeDark: + case ThemeLight: + default: + QApplication::setStyle( QStyleFactory::create( "Fusion" ) ); + } + + QPalette pal; + auto baseC = settings.value( "Settings/Theme/Base Color", defaultsDark[Base] ).value(); + auto baseCAlt = settings.value( "Settings/Theme/Base Color Alt", defaultsDark[BaseAlt] ).value(); + auto baseCTxt = settings.value( "Settings/Theme/Text", defaultsDark[Text] ).value(); + auto baseCHighlight = settings.value( "Settings/Theme/Highlight", defaultsDark[Highlight] ).value(); + auto baseCTxtHighlight = settings.value( "Settings/Theme/Highlight Text", defaultsDark[HighlightText] ).value(); + auto baseCBrightTxt = settings.value( "Settings/Theme/Bright Text", defaultsDark[BrightText] ).value(); + + // Fill the standard palette + pal.setColor( QPalette::Window, baseC ); + pal.setColor( QPalette::WindowText, baseCTxt ); + pal.setColor( QPalette::Base, baseC ); + pal.setColor( QPalette::AlternateBase, baseCAlt ); + pal.setColor( QPalette::ToolTipBase, baseC ); + pal.setColor( QPalette::ToolTipText, baseCTxt ); + pal.setColor( QPalette::Text, baseCTxt ); + pal.setColor( QPalette::Button, baseC ); + pal.setColor( QPalette::ButtonText, baseCTxt ); + pal.setColor( QPalette::BrightText, baseCBrightTxt ); + pal.setColor( QPalette::Link, baseCBrightTxt ); + pal.setColor( QPalette::Highlight, baseCHighlight ); + pal.setColor( QPalette::HighlightedText, baseCTxtHighlight ); + + // Mute the disabled palette + auto baseCDark = baseC.darker( 150 ); + auto baseCAltDark = baseCAlt.darker( 150 ); + auto baseCHighlightDark = QColor( 128, 128, 128 ); + auto baseCTxtDark = Qt::darkGray; + auto baseCTxtHighlightDark = Qt::darkGray; + auto baseCBrightTxtDark = Qt::darkGray; + + pal.setColor( QPalette::ColorGroup::Disabled, QPalette::Window, baseC ); // Leave base color the same + pal.setColor( QPalette::ColorGroup::Disabled, QPalette::WindowText, baseCTxtDark ); + pal.setColor( QPalette::ColorGroup::Disabled, QPalette::Base, baseCDark ); + pal.setColor( QPalette::ColorGroup::Disabled, QPalette::AlternateBase, baseCAltDark ); + pal.setColor( QPalette::ColorGroup::Disabled, QPalette::ToolTipBase, baseCDark ); + pal.setColor( QPalette::ColorGroup::Disabled, QPalette::ToolTipText, baseCTxtDark ); + pal.setColor( QPalette::ColorGroup::Disabled, QPalette::Text, baseCTxtDark ); + pal.setColor( QPalette::ColorGroup::Disabled, QPalette::Button, baseCDark ); + pal.setColor( QPalette::ColorGroup::Disabled, QPalette::ButtonText, baseCTxtDark ); + pal.setColor( QPalette::ColorGroup::Disabled, QPalette::BrightText, baseCBrightTxtDark ); + pal.setColor( QPalette::ColorGroup::Disabled, QPalette::Link, baseCBrightTxtDark ); + pal.setColor( QPalette::ColorGroup::Disabled, QPalette::Highlight, baseCHighlightDark ); + pal.setColor( QPalette::ColorGroup::Disabled, QPalette::HighlightedText, baseCTxtHighlightDark ); + + // Set Palette and Stylesheet + + QDir qssDir( QApplication::applicationDirPath() ); + QStringList qssList( QStringList() + << "style.qss" +#ifdef Q_OS_LINUX + << "/usr/share/nifskope/style.qss" +#endif + ); + QString qssName; + for ( const QString& str : qssList ) { + if ( qssDir.exists( str ) ) { + qssName = qssDir.filePath( str ); + break; + } + } + + // Load stylesheet + QString styleData; + if ( !qssName.isEmpty() ) { + QFile style( qssName ); + if ( style.open( QFile::ReadOnly ) ) { + styleData = style.readAll(); + style.close(); + } + } + + // Remove comments first + QRegularExpression cssComment( R"regex(\/\*[^*]*\*+([^/*][^*]*\*+)*\/)regex" ); + styleData.replace( cssComment, "" ); + + // Theme name for icon path customization + styleData.replace( "${theme}", (theme == ThemeDark) ? "dark" : "light" ); + + // Highlight colors in an "R, G, B" string to combine with opacity in rgba() + auto rgb = QString("%1, %2, %3").arg(baseCHighlight.red()) + .arg(baseCHighlight.green()) + .arg(baseCHighlight.blue()); + styleData.replace( "${rgb}", rgb ); + + qApp->setPalette( pal ); + qApp->setStyleSheet( styleData ); +} + +void NifSkope::setThemeActions() +{ + // Map of QAction object names to QRC alias + QMap names = { + //{"aTextures", "textures"} + }; + + QString themeString = (theme == ThemeDark) ? "dark" : "light"; + for ( auto a : allActions ) { + auto obj = a->objectName(); + if ( names.contains( obj ) ) { + a->setIcon( QIcon( QString(":btn/%1/%2").arg(themeString).arg(names[obj]) ) ); + } + } +} + +void NifSkope::setToolbarSize() +{ + QSize size = {18, 18}; + if ( toolbarSize == ToolbarLarge ) + size = {36, 36}; + + for ( QObject * o : children() ) { + auto tb = qobject_cast(o); + if ( tb ) + tb->setIconSize(size); + } +} + +void NifSkope::setTheme( nstheme::WindowTheme t ) +{ + theme = t; + + QSettings settings; + settings.setValue( "Theme", theme ); + + QColor * defaults = nullptr; + QString iconPrefix; + + // If Dark reset to dark colors and icons + // If Light reset to light colors and icons + switch ( t ) { + case ThemeDark: + defaults = defaultsDark; + break; + case ThemeLight: + defaults = defaultsLight; + break; + default: + break; + } + + if ( defaults ) { + settings.setValue( "Settings/Theme/Base Color", defaults[Base] ); + settings.setValue( "Settings/Theme/Base Color Alt", defaults[BaseAlt] ); + settings.setValue( "Settings/Theme/Text", defaults[Text] ); + settings.setValue( "Settings/Theme/Highlight", defaults[Highlight] ); + settings.setValue( "Settings/Theme/Highlight Text", defaults[HighlightText] ); + settings.setValue( "Settings/Theme/Bright Text", defaults[BrightText] ); + } + + loadTheme(); +} + void NifSkope::resizeDone() { isResizing = false; @@ -1366,3 +1444,10 @@ void NifSkope::on_aSettings_triggered() options->raise(); options->activateWindow(); } + +void NifSkope::on_mTheme_triggered( QAction * action ) +{ + auto newTheme = WindowTheme( action->data().toInt() ); + + setTheme( newTheme ); +} diff --git a/src/ui/about_dialog.cpp b/src/ui/about_dialog.cpp index 3b2a99017..716714608 100644 --- a/src/ui/about_dialog.cpp +++ b/src/ui/about_dialog.cpp @@ -4,6 +4,9 @@ AboutDialog::AboutDialog( QWidget * parent ) : QDialog( parent ) { ui.setupUi( this ); + + setAttribute( Qt::WA_DeleteOnClose ); + #ifdef NIFSKOPE_REVISION this->setWindowTitle( tr( "About NifSkope %1 (revision %2)" ).arg( NIFSKOPE_VERSION, NIFSKOPE_REVISION ) ); #else diff --git a/src/ui/nifskope.ui b/src/ui/nifskope.ui index 296d053f2..3b3783742 100644 --- a/src/ui/nifskope.ui +++ b/src/ui/nifskope.ui @@ -34,11 +34,6 @@ 24 - - - 10 - - false @@ -47,12 +42,8 @@ padding: 0 0 0 0; margin: 0 0 0 0; border: 0px solid transparent; -/*background: transparent; -color: #0099ff; -background: #0099ff; -color: white;*/ color: white; -background: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 rgba(67, 67, 67, 255), stop:0.039823 rgba(85, 85, 85, 255), stop:0.103245 rgba(102, 102, 102, 255), stop:0.171091 rgba(106, 106, 106, 255)); +background: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 rgba(33, 33, 33, 128), stop:0.039823 rgba(42, 42, 42, 128), stop:0.103245 rgba(50, 50, 50, 128), stop:0.171091 rgba(53, 53, 53, 128)); } #statusbar QProgressBar { @@ -132,8 +123,8 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 - 18 - 18 + 36 + 36 @@ -157,8 +148,8 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 - 18 - 18 + 36 + 36 @@ -198,6 +189,12 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 Qt::BottomToolBarArea|Qt::TopToolBarArea + + + 36 + 36 + + TopToolBarArea @@ -223,8 +220,8 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 - 18 - 18 + 36 + 36 @@ -434,7 +431,18 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 Options + + + Theme + + + + + + + + @@ -500,10 +508,7 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 - QFrame::StyledPanel - - - QFrame::Raised + QFrame::NoFrame @@ -740,7 +745,7 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 true - true + false @@ -911,12 +916,6 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 - - QFrame::StyledPanel - - - QFrame::Raised - 6 @@ -967,12 +966,6 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 - - QFrame::StyledPanel - - - QFrame::Raised - 3 @@ -1742,10 +1735,10 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 :/btn/bulb:/btn/bulbOff - Lighting + Do Lighting - Lighting + Do Lighting Turn Lighting On/Off @@ -2046,6 +2039,38 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 Do Skinning + + + true + + + 2.0 Dark + + + + + true + + + Windows + + + + + true + + + Windows XP + + + + + true + + + 2.0 Light + + diff --git a/src/ui/settingsdialog.cpp b/src/ui/settingsdialog.cpp index bc9278b6c..5835faa9a 100644 --- a/src/ui/settingsdialog.cpp +++ b/src/ui/settingsdialog.cpp @@ -36,6 +36,10 @@ SettingsDialog::SettingsDialog( QWidget * parent ) : btnApply = ui->submit->button( QDialogButtonBox::Apply ); btnApply->setEnabled( false ); + btnCancel = ui->submit->button( QDialogButtonBox::Cancel ); + btnCancel->setEnabled( true ); + btnCancel->setText( tr("Close") ); + QSettings settings; settingsVersion = settings.value( "Settings/Version" ); @@ -74,6 +78,7 @@ void SettingsDialog::apply() btnSave->setEnabled( false ); btnApply->setEnabled( false ); + btnCancel->setText( tr("Close") ); } void SettingsDialog::save() @@ -90,6 +95,7 @@ void SettingsDialog::cancel() btnSave->setEnabled( false ); btnApply->setEnabled( false ); + btnCancel->setText( tr("Close") ); } void SettingsDialog::restoreDefaults() @@ -104,6 +110,7 @@ void SettingsDialog::modified() { btnSave->setEnabled( true ); btnApply->setEnabled( true ); + btnCancel->setText( tr("Cancel") ); } void SettingsDialog::changePage( QListWidgetItem * current, QListWidgetItem * previous ) diff --git a/src/ui/settingsdialog.h b/src/ui/settingsdialog.h index d9fa172dd..37e3bfffd 100644 --- a/src/ui/settingsdialog.h +++ b/src/ui/settingsdialog.h @@ -55,6 +55,7 @@ public slots: QPushButton * btnSave; QPushButton * btnApply; + QPushButton * btnCancel; bool eventFilter( QObject *, QEvent * ) override final; void showEvent( QShowEvent * ) override final; diff --git a/src/ui/settingsdialog.ui b/src/ui/settingsdialog.ui index f9d9e07da..b84a6597c 100644 --- a/src/ui/settingsdialog.ui +++ b/src/ui/settingsdialog.ui @@ -16,12 +16,6 @@ - - QFrame::StyledPanel - - - QFrame::Raised - 6 @@ -46,12 +40,6 @@ 0 - - QFrame::StyledPanel - - - QFrame::Raised - 0 @@ -79,16 +67,6 @@ 14 - - QListWidget { outline: none; } -QListWidget::item { height: 20px; padding: 10px; color: #555555; border: 1px solid transparent; } -QListWidget::item::text { padding-left: 10px; background: transparent; border: 1px solid transparent; } -QListWidget::item::text:focus { border: 1px solid transparent; } -QListWidget::item:focus { border: 1px solid transparent; color: black; } - -QListWidget::item:hover { background: rgba(0, 153, 255, 32); border: 1px solid transparent; } -QListWidget::item:selected { background: rgba(0, 153, 255, 128); border: 1px solid transparent; } - QFrame::NoFrame @@ -133,12 +111,6 @@ QListWidget::item:selected { background: rgba(0, 153, 255, 128); border: 1px sol 0 - - QFrame::StyledPanel - - - QFrame::Raised - 6 diff --git a/src/ui/settingsgeneral.ui b/src/ui/settingsgeneral.ui index 9828a1f30..7a4786cf1 100644 --- a/src/ui/settingsgeneral.ui +++ b/src/ui/settingsgeneral.ui @@ -230,10 +230,119 @@ + + + Theme + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + + + + + + + + + + + 0 + 0 + + + + + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + + + + + + + + + + + + + + 0 + 0 + + + + + + + + Large Icons + + + true + + + + + + + + ColorLineEdit + QWidget +

ui/widgets/colorwheel.h
+ 1 + + + ColorWheel + QWidget +
ui/widgets/colorwheel.h
+ 1 +
+ diff --git a/src/ui/settingspane.cpp b/src/ui/settingspane.cpp index d92f9cf3d..a0f912640 100644 --- a/src/ui/settingspane.cpp +++ b/src/ui/settingspane.cpp @@ -7,6 +7,8 @@ #include "ui/widgets/floatslider.h" #include "ui/settingsdialog.h" +#include "nifskope.h" + #include #include @@ -24,6 +26,8 @@ #include #include +using namespace nstheme; + SettingsPane::SettingsPane( QWidget * parent ) : QWidget( parent ) @@ -277,6 +281,23 @@ SettingsGeneral::SettingsGeneral( QWidget * parent ) : } } } + + auto color = [this]( const QString & str, ColorWheel * w, ColorLineEdit * e, const QColor & color ) { + e->setWheel( w, str ); + w->setColor( color ); + + connect( w, &ColorWheel::sigColorEdited, this, &SettingsPane::modifyPane ); + connect( e, &ColorLineEdit::textEdited, this, &SettingsPane::modifyPane ); + }; + + auto colors = NifSkope::defaultsDark; + + color( "Base", ui->colorBaseColor, ui->baseColor, colors[Base] ); + color( "Base Alt", ui->colorBaseColorAlt, ui->baseColorAlt, colors[BaseAlt] ); + color( "Text", ui->colorText, ui->text, colors[Text] ); + color( "Base Highlight", ui->colorHighlight, ui->highlight, colors[Highlight] ); + color( "Text Highlight", ui->colorHighlightText, ui->highlightText, colors[HighlightText] ); + color( "Bright Text", ui->colorBrightText, ui->brightText, colors[BrightText] ); } SettingsGeneral::~SettingsGeneral() @@ -336,6 +357,8 @@ void SettingsGeneral::write() emit dlg->localeChanged(); } + NifSkope::reloadTheme(); + setModified( false ); } @@ -364,7 +387,7 @@ SettingsRender::SettingsRender( QWidget * parent ) : connect( e, &ColorLineEdit::textEdited, this, &SettingsPane::modifyPane ); }; - color( "Background", ui->colorBackground, ui->background, QColor( 0, 0, 0 ) ); + color( "Background", ui->colorBackground, ui->background, QColor( 46, 46, 46 ) ); color( "Wireframe", ui->colorWireframe, ui->wireframe, QColor( 0, 255, 0 ) ); color( "Highlight", ui->colorHighlight, ui->highlight, QColor( 255, 255, 0 ) ); diff --git a/src/ui/settingsrender.ui b/src/ui/settingsrender.ui index 0dfaeae84..41ff8eb3b 100644 --- a/src/ui/settingsrender.ui +++ b/src/ui/settingsrender.ui @@ -615,15 +615,15 @@ - ColorWheel + ColorLineEdit QWidget -
widgets/colorwheel.h
+
ui/widgets/colorwheel.h
1
- ColorLineEdit + ColorWheel QWidget -
widgets/colorwheel.h
+
ui/widgets/colorwheel.h
1
diff --git a/src/ui/settingsresources.ui b/src/ui/settingsresources.ui index 6159389bc..9a1dd45ce 100644 --- a/src/ui/settingsresources.ui +++ b/src/ui/settingsresources.ui @@ -65,12 +65,6 @@ - - QFrame::StyledPanel - - - QFrame::Raised - 0 @@ -89,12 +83,6 @@ QPushButton { padding: 5px 10px; } - - QFrame::StyledPanel - - - QFrame::Raised - 0 @@ -179,12 +167,6 @@ Game Paths - - QFrame::StyledPanel - - - QFrame::Raised - 0 @@ -203,12 +185,6 @@ Game Paths QPushButton { padding: 5px 10px; } - - QFrame::StyledPanel - - - QFrame::Raised - 0 diff --git a/src/ui/widgets/floatslider.cpp b/src/ui/widgets/floatslider.cpp index 9a0b19b6a..a3d092867 100644 --- a/src/ui/widgets/floatslider.cpp +++ b/src/ui/widgets/floatslider.cpp @@ -78,8 +78,7 @@ void FloatSliderEditBox::show( const QPoint & pos ) move( pos ); QWidget::show(); setFocus( Qt::PopupFocusReason ); - // Leave as old signal syntax for now. Casting is too ugly here. - connect( QApplication::instance(), SIGNAL(focusChanged( QWidget *, QWidget * )), this, SLOT(focusChanged( QWidget *, QWidget * )) ); + connect( qApp, &QApplication::focusChanged, this, &FloatSliderEditBox::focusChanged ); } void FloatSliderEditBox::hide() @@ -87,8 +86,8 @@ void FloatSliderEditBox::hide() if ( !isVisible() ) { return; } - // Leave as old signal syntax for now. Casting is too ugly here. - disconnect( QApplication::instance(), SIGNAL(focusChanged( QWidget *, QWidget * )), this, SLOT(focusChanged( QWidget *, QWidget * )) ); + + disconnect( qApp, &QApplication::focusChanged, this, &FloatSliderEditBox::focusChanged ); QWidget::hide(); } @@ -357,9 +356,10 @@ QSize FloatSlider::sizeHint() const int h = 84; if ( ori == Qt::Horizontal ) { - int x = h; - h = w; - w = x; + // Minimum size for clipping issues in Fusion theme + int x = std::max( w, 24 ); + w = h; + h = x; } return QSize( w, h ); diff --git a/src/ui/widgets/lightingwidget.cpp b/src/ui/widgets/lightingwidget.cpp new file mode 100644 index 000000000..5ccdbd993 --- /dev/null +++ b/src/ui/widgets/lightingwidget.cpp @@ -0,0 +1,73 @@ +#include "lightingwidget.h" +#include "ui_lightingwidget.h" + +#include "glview.h" + + +// Slider lambda +auto sld = []( QSlider * slider, int min, int max, int val ) { + slider->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Maximum ); + slider->setRange( min, max ); + slider->setSingleStep( max / 4 ); + slider->setTickInterval( max / 2 ); + slider->setTickPosition( QSlider::TicksBelow ); + slider->setValue( val ); +}; + +LightingWidget::LightingWidget( GLView * ogl, QWidget * parent ) : QWidget(parent), + ui(new Ui::LightingWidget) +{ + ui->setupUi(this); + + setDefaults(); + + ui->sldDeclination->setDisabled( ui->btnFrontal->isChecked() ); + ui->sldPlanarAngle->setDisabled( ui->btnFrontal->isChecked() ); + + // Disable lighting sliders when Frontal + connect( ui->btnFrontal, &QToolButton::toggled, ui->sldDeclination, &QSlider::setDisabled ); + connect( ui->btnFrontal, &QToolButton::toggled, ui->sldPlanarAngle, &QSlider::setDisabled ); + + // Disable Frontal checkbox (and sliders) when no lighting + connect( ui->btnLighting, &QToolButton::toggled, ui->btnFrontal, &QToolButton::setEnabled ); + connect( ui->btnLighting, &QToolButton::toggled, [&]( bool checked ) { + if ( !ui->btnFrontal->isChecked() ) { + // Don't enable the sliders if Frontal is checked + ui->sldDeclination->setEnabled( checked ); + ui->sldPlanarAngle->setEnabled( checked ); + } + } ); + + // Inform ogl of changes + connect( ui->sldDirectional, &QSlider::valueChanged, ogl, &GLView::setBrightness ); + connect( ui->sldAmbient, &QSlider::valueChanged, ogl, &GLView::setAmbient ); + connect( ui->sldDeclination, &QSlider::valueChanged, ogl, &GLView::setDeclination ); + connect( ui->sldPlanarAngle, &QSlider::valueChanged, ogl, &GLView::setPlanarAngle ); + connect( ui->btnFrontal, &QToolButton::toggled, ogl, &GLView::setFrontalLight ); +} + +LightingWidget::~LightingWidget() +{ +} + +void LightingWidget::setDefaults() +{ + sld( ui->sldDirectional, DirMin, DirMax, DirDefault ); + sld( ui->sldAmbient, AmbientMin, AmbientMax, AmbientDefault ); + sld( ui->sldDeclination, DeclinationMin, DeclinationMax, DeclinationDefault ); + sld( ui->sldPlanarAngle, PlanarAngleMin, PlanarAngleMax, PlanarAngleDefault ); +} + +void LightingWidget::setActions( QVector atns ) +{ + ui->btnLighting->setDefaultAction( atns.value(0) ); + ui->btnTextures->setDefaultAction( atns.value(1) ); + ui->btnVertexColors->setDefaultAction( atns.value(2) ); + ui->btnSpecular->setDefaultAction( atns.value(3) ); + ui->btnCubemap->setDefaultAction( atns.value(4) ); + ui->btnGlow->setDefaultAction( atns.value(5) ); + ui->btnLightingOnly->setDefaultAction( atns.value(6) ); + ui->btnSilhouette->setDefaultAction( atns.value(7) ); + + connect( ui->btnLighting, &QToolButton::toggled, atns.value(3), &QAction::setEnabled ); +} diff --git a/src/ui/widgets/lightingwidget.h b/src/ui/widgets/lightingwidget.h new file mode 100644 index 000000000..0524bcfbf --- /dev/null +++ b/src/ui/widgets/lightingwidget.h @@ -0,0 +1,50 @@ +#ifndef LIGHTINGWIDGET_H +#define LIGHTINGWIDGET_H + +#include + +#include + +class GLView; +class QAction; + +namespace Ui { +class LightingWidget; +} + +class LightingWidget : public QWidget +{ + Q_OBJECT + +public: + LightingWidget( GLView * ogl, QWidget * parent = nullptr); + ~LightingWidget(); + + void setDefaults(); + void setActions( QVector actions ); + +private: + std::unique_ptr ui; + + enum + { + BRIGHT = 1440, + POS = 720, + + DirMin = 0, + DirMax = BRIGHT, + AmbientMin = 0, + AmbientMax = BRIGHT, + DeclinationMin = -POS, + DeclinationMax = POS, + PlanarAngleMin = -POS, + PlanarAngleMax = POS, + + DirDefault = DirMax / 2, + AmbientDefault = AmbientMax * 3 / 8, + DeclinationDefault = (DeclinationMax + DeclinationMin), + PlanarAngleDefault = (PlanarAngleMax + PlanarAngleMin) + }; +}; + +#endif // LIGHTINGWIDGET_H diff --git a/src/ui/widgets/lightingwidget.ui b/src/ui/widgets/lightingwidget.ui new file mode 100644 index 000000000..c856318d0 --- /dev/null +++ b/src/ui/widgets/lightingwidget.ui @@ -0,0 +1,367 @@ + + + LightingWidget + + + + 0 + 0 + 240 + 320 + + + + + 8 + + + 8 + + + 8 + + + 8 + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Do Lighting + + + + :/btn/bulbOff + :/btn/bulb:/btn/bulbOff + + + + 24 + 24 + + + + true + + + true + + + Qt::ToolButtonTextBesideIcon + + + + + + + + 0 + 0 + + + + Frontal Light + + + true + + + true + + + + + + + + + + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + + + + + + 24 + 24 + + + + + + + + :/btn/lightVertical:/btn/lightVertical + + + + 18 + 18 + + + + true + + + + + + + + 24 + 24 + + + + + + + + :/btn/lightHorizontal:/btn/lightHorizontal + + + + 18 + 18 + + + + true + + + + + + + + 24 + 24 + + + + + + + + :/btn/sun:/btn/sun + + + + 18 + 18 + + + + true + + + + + + + + 24 + 24 + + + + + + + + :/btn/cloud:/btn/cloud + + + + 18 + 18 + + + + true + + + + + + + + + + + 0 + 0 + + + + + + + ... + + + + 36 + 36 + + + + true + + + true + + + + + + + ... + + + + 36 + 36 + + + + true + + + true + + + + + + + ... + + + + 36 + 36 + + + + + + + + ... + + + + 36 + 36 + + + + + + + + ... + + + + 36 + 36 + + + + true + + + + + + + ... + + + + 36 + 36 + + + + true + + + + + + + ... + + + + 36 + 36 + + + + + + + + + + + + + + From 0e1cc2c17c8e57d0a4604bacab147301e8a27543 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 18 Jul 2017 19:52:00 -0400 Subject: [PATCH 055/152] Change auto to auto& from d656dfc Sloppy auto declaration caused a bug in controllers.cpp which prevented animations from playing. Made the other auto& as well. --- lib/fsengine/bsa.cpp | 2 +- src/gl/controllers.cpp | 2 +- src/nifskope.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/fsengine/bsa.cpp b/lib/fsengine/bsa.cpp index a2fb1b1d1..88b9255a3 100644 --- a/lib/fsengine/bsa.cpp +++ b/lib/fsengine/bsa.cpp @@ -760,7 +760,7 @@ bool BSA::scan( const BSA::BSAFolder * folder, QStandardItem * item, QString pat if ( !folder || folder->children.count() == 0 ) return false; - auto children = folder->children; + auto& children = folder->children; QHash::const_iterator i; for ( i = children.begin(); i != children.end(); ++i ) { auto f = i.value()->files; diff --git a/src/gl/controllers.cpp b/src/gl/controllers.cpp index 59522220b..8c75115a0 100644 --- a/src/gl/controllers.cpp +++ b/src/gl/controllers.cpp @@ -337,7 +337,7 @@ bool MultiTargetTransformController::setInterpolator( Node * node, const QModelI while ( it.hasNext() ) { it.next(); - auto val = it.value(); + auto& val = it.value(); if ( val.first == node ) { if ( val.second ) { delete val.second; diff --git a/src/nifskope.cpp b/src/nifskope.cpp index 02c5dc4af..f4bd893a6 100644 --- a/src/nifskope.cpp +++ b/src/nifskope.cpp @@ -107,7 +107,7 @@ QString NifSkope::fileFilter( const QString & ext ) QString filter; for ( int i = 0; i < filetypes.size(); i++ ) { - auto ft = filetypes.at(i); + auto& ft = filetypes.at(i); if ( ft.second == ext ) filter = QString( "%1 (*.%2)" ).arg( ft.first ).arg( ft.second ); } From 910554b40f4815b6ae4b5f8400de74820a6b0372 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 18 Jul 2017 20:15:28 -0400 Subject: [PATCH 056/152] [UI] Fix checkbox list crash Finally determined after many years the reasoning behind the random and non-reproducible crash when editing bitflags. `editor->setGeometry` in `CheckBoxListDelegate::updateEditorGeometry` would run on each item hovered on but only sometimes did this crash. It turns out it is because of the item view having auto-scroll on by default. Trying to call setGeometry with auto-scroll on could crash randomly. Threw in some null checking for good measure though it was unnecessary. --- src/ui/widgets/nifcheckboxlist.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/ui/widgets/nifcheckboxlist.cpp b/src/ui/widgets/nifcheckboxlist.cpp index f8e1ad465..f60fca7a3 100644 --- a/src/ui/widgets/nifcheckboxlist.cpp +++ b/src/ui/widgets/nifcheckboxlist.cpp @@ -59,6 +59,12 @@ CheckBoxList::CheckBoxList( QWidget * widget ) // it just cool to have it as default ;) view()->setAlternatingRowColors( true ); + + // Turn off Auto Scroll to prevent crash + view()->setAutoScroll( false ); + view()->setHorizontalScrollMode( QAbstractItemView::ScrollMode::ScrollPerItem ); + view()->setVerticalScrollMode( QAbstractItemView::ScrollMode::ScrollPerItem ); + view()->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOn ); } @@ -139,6 +145,9 @@ void CheckBoxListDelegate::setEditorData( QWidget * editor, const QModelIndex & { //set editor data QCheckBox * myEditor = static_cast(editor); + if ( !myEditor || !index.isValid() ) + return; + myEditor->setText( index.data( Qt::DisplayRole ).toString() ); myEditor->setChecked( index.data( Qt::UserRole ).toBool() ); } @@ -147,6 +156,9 @@ void CheckBoxListDelegate::setModelData( QWidget * editor, QAbstractItemModel * { //get the value from the editor (CheckBox) QCheckBox * myEditor = static_cast(editor); + if ( !myEditor || !model || !index.isValid() ) + return; + bool value = myEditor->isChecked(); //set model data @@ -156,9 +168,12 @@ void CheckBoxListDelegate::setModelData( QWidget * editor, QAbstractItemModel * model->setItemData( index, data ); } -void CheckBoxListDelegate::updateEditorGeometry( QWidget * editor, const QStyleOptionViewItem & option, const QModelIndex & index ) const +void CheckBoxListDelegate::updateEditorGeometry( QWidget * editor, const QStyleOptionViewItem & option, + const QModelIndex & index ) const { - Q_UNUSED( index ); + if ( !editor || !index.isValid() ) + return; + editor->setGeometry( option.rect ); } From 5615e2c9d4fb1be541c6002127a9fdb88367dcf1 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 18 Jul 2017 20:19:08 -0400 Subject: [PATCH 057/152] [GL] Correct BGSM priority over flags Double sided, depth test, and depth write flags were ignored in material files which is not only incorrect, but some vanilla files actually rely solely on the BGSM for these values so they appear broken. --- src/gl/glmesh.cpp | 6 +++--- src/gl/glproperty.cpp | 8 +++++++- src/gl/renderer.cpp | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/gl/glmesh.cpp b/src/gl/glmesh.cpp index 58b33980e..eaf850ca5 100644 --- a/src/gl/glmesh.cpp +++ b/src/gl/glmesh.cpp @@ -152,9 +152,9 @@ void Shape::updateShaderProperties( const NifModel * nif ) if ( !bssp ) return; - depthTest = bssp->getFlags1() & ShaderFlags::SLSF1_ZBuffer_Test; - depthWrite = bssp->getFlags2() & ShaderFlags::SLSF2_ZBuffer_Write; - isDoubleSided = bssp->getFlags2() & ShaderFlags::SLSF2_Double_Sided; + depthTest = bssp->getDepthTest(); + depthWrite = bssp->getDepthWrite(); + isDoubleSided = bssp->getIsDoubleSided(); isVertexAlphaAnimation = bssp->getFlags2() & ShaderFlags::SLSF2_Tree_Anim; } diff --git a/src/gl/glproperty.cpp b/src/gl/glproperty.cpp index 9619ae897..f166000d8 100644 --- a/src/gl/glproperty.cpp +++ b/src/gl/glproperty.cpp @@ -1113,6 +1113,10 @@ void BSLightingShaderProperty::updateParams( const NifModel * nif, const QModelI hasVertexColors = hasSF2( ShaderFlags::SLSF2_Vertex_Colors ); if ( !m ) { + isDoubleSided = hasSF2( ShaderFlags::SLSF2_Double_Sided ); + depthTest = hasSF1( ShaderFlags::SLSF1_ZBuffer_Test ); + depthWrite = hasSF2( ShaderFlags::SLSF2_ZBuffer_Write ); + alpha = nif->get( prop, "Alpha" ); auto scale = nif->get( prop, "UV Scale" ); @@ -1234,7 +1238,9 @@ void BSLightingShaderProperty::updateParams( const NifModel * nif, const QModelI hasSoftlight = m->bSubsurfaceLighting; rimPower = m->fRimPower; backlightPower = m->fBacklightPower; - + isDoubleSided = m->bTwoSided; + depthTest = m->bZBufferTest; + depthWrite = m->bZBufferWrite; hasEnvironmentMap = m->bEnvironmentMapping; hasCubeMap = m->bEnvironmentMapping && !m->textureList[4].isEmpty(); diff --git a/src/gl/renderer.cpp b/src/gl/renderer.cpp index f9675026f..8712a279b 100644 --- a/src/gl/renderer.cpp +++ b/src/gl/renderer.cpp @@ -789,7 +789,7 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & } if ( nif->getUserVersion2() == 130 ) { - uni1i( "doubleSided", mesh->isDoubleSided ); + uni1i( "doubleSided", mesh->bslsp->getIsDoubleSided() ); uni1f( "paletteScale", mesh->bslsp->paletteScale ); uni1f( "subsurfaceRolloff", mesh->bslsp->getLightingEffect1() ); uni1f( "fresnelPower", mesh->bslsp->fresnelPower ); From a4ea44bc712fcf3f713a08fe0e3086c29093689f Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 18 Jul 2017 20:23:20 -0400 Subject: [PATCH 058/152] [FO4] Expand classification for Editor Markers FO4 contains additional strings which are special cased for editor markers, such as "VisibilityEditorMarker". --- src/gl/bsshape.cpp | 2 +- src/spells/sanitize.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gl/bsshape.cpp b/src/gl/bsshape.cpp index a0a4517c8..9d9714801 100644 --- a/src/gl/bsshape.cpp +++ b/src/gl/bsshape.cpp @@ -331,7 +331,7 @@ void BSShape::drawShapes( NodeList * secondPass, bool presort ) glPointSize( 8.5 ); // TODO: Only run this if BSXFlags has "EditorMarkers present" flag - if ( !(scene->options & Scene::ShowMarkers) && name.startsWith( "EditorMarker" ) ) + if ( !(scene->options & Scene::ShowMarkers) && name.contains( "EditorMarker" ) ) return; if ( Node::SELECTING ) { diff --git a/src/spells/sanitize.cpp b/src/spells/sanitize.cpp index defad84d6..1b83dce64 100644 --- a/src/spells/sanitize.cpp +++ b/src/spells/sanitize.cpp @@ -405,7 +405,7 @@ class spFixInvalidNames final : public Spell auto nameString = nif->get( iBlock, "Name" ); // Ignore Editor Markers and AddOnNodes - if ( nameString.startsWith( "EditorMarker" ) || nif->inherits( iBlock, "BSValueNode" ) ) + if ( nameString.contains( "EditorMarker" ) || nif->inherits( iBlock, "BSValueNode" ) ) continue; QModelIndex iBlockParent = nif->getBlock( nif->getParent( i ) ); From 07f4b5e3d8989c3e0985cc0381fa7cedf726f5a5 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 18 Jul 2017 20:24:10 -0400 Subject: [PATCH 059/152] [Spell] Update UV naming for nif.xml --- src/spells/strippify.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/spells/strippify.cpp b/src/spells/strippify.cpp index 4b94d35aa..40f750863 100644 --- a/src/spells/strippify.cpp +++ b/src/spells/strippify.cpp @@ -88,7 +88,7 @@ class spStrippify final : public Spell copyValue( nif, iStripData, iData, "Has UV" ); copyValue( nif, iStripData, iData, "Num UV Sets" ); copyValue( nif, iStripData, iData, "Vector Flags" ); - copyValue( nif, iStripData, iData, "BS Num UV Sets" ); + copyValue( nif, iStripData, iData, "BS Vector Flags" ); copyValue( nif, iStripData, iData, "Num UV Sets 2" ); QModelIndex iDstUV = nif->getIndex( iStripData, "UV Sets" ); QModelIndex iSrcUV = nif->getIndex( iData, "UV Sets" ); @@ -243,8 +243,8 @@ class spTriangulate final : public Spell copyValue( nif, iTriData, iStripData, "Has UV" ); copyValue( nif, iTriData, iStripData, "Num UV Sets" ); - copyValue( nif, iTriData, iStripData, "BS Num UV Sets" ); copyValue( nif, iTriData, iStripData, "Vector Flags" ); + copyValue( nif, iTriData, iStripData, "BS Vector Flags" ); copyValue( nif, iTriData, iStripData, "Num UV Sets 2" ); QModelIndex iDstUV = nif->getIndex( iTriData, "UV Sets" ); QModelIndex iSrcUV = nif->getIndex( iStripData, "UV Sets" ); From d35cce5e9cfffda4b4e8be1c79ea599220b2c7c7 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Wed, 19 Jul 2017 09:50:04 -0400 Subject: [PATCH 060/152] [UI] Open UV Editor even if there is no texture --- src/ui/widgets/uvedit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/widgets/uvedit.cpp b/src/ui/widgets/uvedit.cpp index 4fb3ef2cc..5261158ff 100644 --- a/src/ui/widgets/uvedit.cpp +++ b/src/ui/widgets/uvedit.cpp @@ -949,7 +949,7 @@ bool UVWidget::setNifData( NifModel * nifModel, const QModelIndex & nifIndex ) } } - return false; + return true; } bool UVWidget::setTexCoords() From 94dc6e9caa6700efccf864d936a6aee4fd5545d8 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Fri, 21 Jul 2017 15:32:53 -0400 Subject: [PATCH 061/152] [Build] Update AppVeyor and Travis to Qt 5.7 --- .travis.yml | 6 +++--- appveyor.yml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 565d7d025..e5a34a7fb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,12 +7,12 @@ os: addons: apt: sources: - - sourceline: 'ppa:beineri/opt-qt551-trusty' + - sourceline: 'ppa:beineri/opt-qt57-trusty' packages: [ # static analysis clang-3.6, # qt5 requirement - qt55-meta-minimal + qt57-meta-minimal ] matrix: fast_finish: true @@ -26,7 +26,7 @@ matrix: before_install: - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then QT_ENV_SCRIPT=$(find /opt -name 'qt*-env.sh'); source $QT_ENV_SCRIPT; fi - - if [ "${TRAVIS_OS_NAME}" = "osx" ]; then brew update; brew install qt@5.5; export PATH="/usr/local/opt/qt@5.5/bin:$PATH"; fi + - if [ "${TRAVIS_OS_NAME}" = "osx" ]; then brew update; brew install qt@5.7; export PATH="/usr/local/opt/qt@5.7/bin:$PATH"; fi script: - qmake --version diff --git a/appveyor.yml b/appveyor.yml index fae521657..c749cb52a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -20,8 +20,8 @@ clone_depth: 1 clone_folder: C:\projects\nifskope install: - - if %PLATFORM%==win32 set QTDIR=C:\Qt\5.6\msvc2015 - - if %PLATFORM%==x64 set QTDIR=C:\Qt\5.6\msvc2015_64 + - if %PLATFORM%==win32 set QTDIR=C:\Qt\5.7\msvc2015 + - if %PLATFORM%==x64 set QTDIR=C:\Qt\5.7\msvc2015_64 - set PATH=%PATH%;%QTDIR%\bin; build_script: From 6a9b86816d6f7dc917e191142d60d9d17776a297 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Fri, 21 Jul 2017 15:40:28 -0400 Subject: [PATCH 062/152] [Build] Fix Travis ppa for 5.7.1 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e5a34a7fb..8d222a9ad 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ os: addons: apt: sources: - - sourceline: 'ppa:beineri/opt-qt57-trusty' + - sourceline: 'ppa:beineri/opt-qt571-trusty' packages: [ # static analysis clang-3.6, From d6e906b35e5ab895e61a4cb580b4c95aff33d75c Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Fri, 21 Jul 2017 17:18:28 -0400 Subject: [PATCH 063/152] [Build] Attempt to fix Travis issues --- .travis.yml | 6 ++++++ src/gl/glcontroller.h | 3 ++- src/model/basemodel.h | 4 ++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8d222a9ad..30202d4c6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,9 +25,15 @@ matrix: - env: ANALYZE="scan-build-3.6 --use-cc clang-3.6 --use-c++ clang++-3.6 " before_install: + - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test; fi + - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then sudo apt-get update -qq; fi - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then QT_ENV_SCRIPT=$(find /opt -name 'qt*-env.sh'); source $QT_ENV_SCRIPT; fi - if [ "${TRAVIS_OS_NAME}" = "osx" ]; then brew update; brew install qt@5.7; export PATH="/usr/local/opt/qt@5.7/bin:$PATH"; fi +install: + - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then sudo apt-get install -qq g++-6; fi + - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-6 90; fi + script: - qmake --version - qmake -makefile NifSkope.pro diff --git a/src/gl/glcontroller.h b/src/gl/glcontroller.h index 973fe9e46..41ce73479 100644 --- a/src/gl/glcontroller.h +++ b/src/gl/glcontroller.h @@ -33,6 +33,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef GLCONTROLLER_H #define GLCONTROLLER_H +#include "model/nifmodel.h" + #include // Inherited #include #include @@ -40,7 +42,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //! @file glcontroller.h Controller, Interpolator, TransformInterpolator, BSplineTransformInterpolator -class NifModel; class Transform; //! Something which can be attached to anything Controllable diff --git a/src/model/basemodel.h b/src/model/basemodel.h index de8199113..fa1b2bdf7 100644 --- a/src/model/basemodel.h +++ b/src/model/basemodel.h @@ -51,6 +51,10 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. class TestMessage; class QAbstractItemDelegate; +class NifIStream; +class NifOStream; +class NifSStream; + /*! Base class for NIF and KFM models, which store files in memory. * * This class serves as an abstract base class for NifModel and KfmModel From 7c4102b711439f3b79abe318c1a3aef68ef8dcbd Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Fri, 21 Jul 2017 17:31:07 -0400 Subject: [PATCH 064/152] [Build] Fix 6da8175 for non-Windows --- lib/fsengine/fsmanager.cpp | 2 ++ src/ui/settingspane.cpp | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/lib/fsengine/fsmanager.cpp b/lib/fsengine/fsmanager.cpp index f3e1b1143..33c5179c3 100644 --- a/lib/fsengine/fsmanager.cpp +++ b/lib/fsengine/fsmanager.cpp @@ -100,6 +100,7 @@ void FSManager::initialize() QStringList FSManager::regPathBSAList( QString regKey, QString dataDir ) { QStringList list; +#ifdef Q_OS_WIN32 QSettings reg( regKey, QSettings::Registry32Format ); QString dataPath = reg.value( "Installed Path" ).toString(); if ( ! dataPath.isEmpty() ) @@ -113,6 +114,7 @@ QStringList FSManager::regPathBSAList( QString regKey, QString dataDir ) list << QDir::fromNativeSeparators(dataPath + QDir::separator() + fn); } } +#endif return list; } diff --git a/src/ui/settingspane.cpp b/src/ui/settingspane.cpp index a0f912640..55f6d5bf7 100644 --- a/src/ui/settingspane.cpp +++ b/src/ui/settingspane.cpp @@ -468,6 +468,7 @@ void SettingsRender::setDefault() bool regFolderPath( QStringList & gamePaths, const QString & regPath, const QString & regValue, const QString & gameFolder, QStringList gameSubDirs = QStringList(), QStringList gameArchiveFilters = QStringList() ) { +#ifdef Q_OS_WIN32 QSettings reg( regPath, QSettings::Registry32Format ); QDir dir( reg.value( regValue ).toString() ); @@ -494,6 +495,7 @@ bool regFolderPath( QStringList & gamePaths, const QString & regPath, const QStr } return true; } +#endif return false; } @@ -670,6 +672,7 @@ void SettingsResources::on_btnFolderUp_clicked() void SettingsResources::on_btnFolderAutoDetect_clicked() { +#ifdef Q_OS_WIN32 // List to hold all games paths that were detected QStringList list; @@ -736,6 +739,7 @@ void SettingsResources::on_btnFolderAutoDetect_clicked() ui->foldersList->setCurrentIndex( folders->index( 0, 0, QModelIndex() ) ); modifyPane(); +#endif } From 919cd9a076fdff5e96dcd37e14037109d48c3e22 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sun, 23 Jul 2017 22:04:32 -0400 Subject: [PATCH 065/152] [Build] Support cygwin64 for sed path --- NifSkope_functions.pri | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NifSkope_functions.pri b/NifSkope_functions.pri index 42da7b9d8..9c4e1fb5b 100644 --- a/NifSkope_functions.pri +++ b/NifSkope_functions.pri @@ -26,12 +26,15 @@ defineReplace(getSed) { GNUWIN32 = $${PROG}/GnuWin32/bin CYGWIN = C:/cygwin/bin + CYGWIN64 = C:/cygwin64/bin SEDPATH = /sed.exe exists($${GNUWIN32}$${SEDPATH}) { sedbin = $${GNUWIN32}$${SEDPATH} } else:exists($${CYGWIN}$${SEDPATH}) { sedbin = $${CYGWIN}$${SEDPATH} + } else:exists($${CYGWIN64}$${SEDPATH}) { + sedbin = $${CYGWIN64}$${SEDPATH} } else { #message(Neither GnuWin32 or Cygwin were found) sedbin = $$system(where sed 2> NUL) From a10d928e85512c18c50595992dee2bbdd8ee1578 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sat, 23 Sep 2017 09:44:37 -0400 Subject: [PATCH 066/152] Fix longstanding bug with updating MOPP byte array MOPP appears to not have been updated correctly since 1.1.x. --- src/spells/moppcode.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/spells/moppcode.cpp b/src/spells/moppcode.cpp index 442f20e2f..d21ca9721 100644 --- a/src/spells/moppcode.cpp +++ b/src/spells/moppcode.cpp @@ -222,16 +222,12 @@ class spMoppCode final : public Spell nif->set( iCodeScale, scale ); QModelIndex iCodeSize = nif->getIndex( ibhkMoppBvTreeShape, "MOPP Data Size" ); - QModelIndex iCode = nif->getIndex( ibhkMoppBvTreeShape, "MOPP Data" ); + QModelIndex iCode = nif->getIndex( ibhkMoppBvTreeShape, "MOPP Data" ).child( 0, 0 ); if ( iCodeSize.isValid() && iCode.isValid() ) { nif->set( iCodeSize, moppcode.size() ); nif->updateArray( iCode ); - - //nif->set( iCode, moppcode ); - for ( int i = 0; i < moppcode.size(); ++i ) { - nif->set( iCode.child( i, 0 ), moppcode[i] ); - } + nif->set( iCode, moppcode ); } } From 5d8c33a77c57a3a0ebdef82bc90eb2b6441a71a1 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sat, 23 Sep 2017 09:47:47 -0400 Subject: [PATCH 067/152] Syncing with nifxml 0.9 changes Changes for upcoming nifxml 0.9. --- src/data/nifvalue.cpp | 1 + src/gl/controllers.cpp | 8 +-- src/gl/glcontroller.cpp | 30 +++++----- src/gl/glproperty.cpp | 6 +- src/gl/gltexloaders.cpp | 116 ++++++++++++++++----------------------- src/spells/animation.cpp | 5 +- src/spells/blocks.cpp | 5 +- src/spells/light.cpp | 3 +- src/spells/texture.cpp | 6 +- src/xml/nifxml.cpp | 2 +- 10 files changed, 81 insertions(+), 101 deletions(-) diff --git a/src/data/nifvalue.cpp b/src/data/nifvalue.cpp index a8d78adbc..a72ca3994 100644 --- a/src/data/nifvalue.cpp +++ b/src/data/nifvalue.cpp @@ -92,6 +92,7 @@ void NifValue::initialize() typeMap.insert( "Quaternion", NifValue::tQuat ); typeMap.insert( "QuaternionWXYZ", NifValue::tQuat ); typeMap.insert( "QuaternionXYZW", NifValue::tQuatXYZW ); + typeMap.insert( "hkQuaternion", NifValue::tQuatXYZW ); typeMap.insert( "Matrix33", NifValue::tMatrix ); typeMap.insert( "Matrix44", NifValue::tMatrix4 ); typeMap.insert( "Vector2", NifValue::tVector2 ); diff --git a/src/gl/controllers.cpp b/src/gl/controllers.cpp index 8c75115a0..aa6050a3d 100644 --- a/src/gl/controllers.cpp +++ b/src/gl/controllers.cpp @@ -136,17 +136,17 @@ void ControllerManager::setSequence( const QString & seqname ) ctrltype = idx.sibling( idx.row(), NifModel::ValueCol ).data( NifSkopeDisplayRole ).toString(); } - QString var1 = nif->get( iCB, "Variable 1" ); + QString var1 = nif->get( iCB, "Controller ID" ); if ( var1.isEmpty() ) { - QModelIndex idx = nif->getIndex( iCB, "Variable 1 Offset" ); + QModelIndex idx = nif->getIndex( iCB, "Controller ID Offset" ); var1 = idx.sibling( idx.row(), NifModel::ValueCol ).data( NifSkopeDisplayRole ).toString(); } - QString var2 = nif->get( iCB, "Variable 2" ); + QString var2 = nif->get( iCB, "Interpolator ID" ); if ( var2.isEmpty() ) { - QModelIndex idx = nif->getIndex( iCB, "Variable 2 Offset" ); + QModelIndex idx = nif->getIndex( iCB, "Interpolator ID Offset" ); var2 = idx.sibling( idx.row(), NifModel::ValueCol ).data( NifSkopeDisplayRole ).toString(); } diff --git a/src/gl/glcontroller.cpp b/src/gl/glcontroller.cpp index f513063da..547fbea4a 100644 --- a/src/gl/glcontroller.cpp +++ b/src/gl/glcontroller.cpp @@ -754,24 +754,26 @@ bool BSplineTransformInterpolator::update( const NifModel * nif, const QModelInd iBasis = nif->getBlock( nif->getLink( index, "Basis Data" ) ); if ( iSpline.isValid() ) - iControl = nif->getIndex( iSpline, "Short Control Points" ); + iControl = nif->getIndex( iSpline, "Compact Control Points" ); if ( iBasis.isValid() ) nCtrl = nif->get( iBasis, "Num Control Points" ); - lTrans = nif->getIndex( index, "Translation" ); - lRotate = nif->getIndex( index, "Rotation" ); - lScale = nif->getIndex( index, "Scale" ); - - lTransOff = nif->get( index, "Translation Offset" ); - lRotateOff = nif->get( index, "Rotation Offset" ); - lScaleOff = nif->get( index, "Scale Offset" ); - lTransMult = nif->get( index, "Translation Multiplier" ); - lRotateMult = nif->get( index, "Rotation Multiplier" ); - lScaleMult = nif->get( index, "Scale Multiplier" ); - lTransBias = nif->get( index, "Translation Bias" ); - lRotateBias = nif->get( index, "Rotation Bias" ); - lScaleBias = nif->get( index, "Scale Bias" ); + auto trans = nif->getIndex( index, "Transform" ); + + lTrans = nif->getIndex( trans, "Translation" ); + lRotate = nif->getIndex( trans, "Rotation" ); + lScale = nif->getIndex( trans, "Scale" ); + + lTransOff = nif->get( index, "Translation Handle" ); + lRotateOff = nif->get( index, "Rotation Handle" ); + lScaleOff = nif->get( index, "Scale Handle" ); + lTransMult = nif->get( index, "Translation Offset" ); + lRotateMult = nif->get( index, "Rotation Offset" ); + lScaleMult = nif->get( index, "Scale Offset" ); + lTransBias = nif->get( index, "Translation Half Range" ); + lRotateBias = nif->get( index, "Rotation Half Range" ); + lScaleBias = nif->get( index, "Scale Half Range" ); return true; } diff --git a/src/gl/glproperty.cpp b/src/gl/glproperty.cpp index f166000d8..863828ed4 100644 --- a/src/gl/glproperty.cpp +++ b/src/gl/glproperty.cpp @@ -387,9 +387,9 @@ void TexturingProperty::update( const NifModel * nif, const QModelIndex & proper if ( textures[t].hasTransform ) { textures[t].translation = nif->get( iTex, "Translation" ); - textures[t].tiling = nif->get( iTex, "Tiling" ); - textures[t].rotation = nif->get( iTex, "W Rotation" ); - textures[t].center = nif->get( iTex, "Center Offset" ); + textures[t].tiling = nif->get( iTex, "Scale" ); + textures[t].rotation = nif->get( iTex, "Rotation" ); + textures[t].center = nif->get( iTex, "Center" ); } else { // we don't really need to set these since they won't be applied in bind() unless hasTransform is set textures[t].translation = Vector2(); diff --git a/src/gl/gltexloaders.cpp b/src/gl/gltexloaders.cpp index 6a0c161c8..2d532ad8a 100644 --- a/src/gl/gltexloaders.cpp +++ b/src/gl/gltexloaders.cpp @@ -1045,14 +1045,10 @@ bool texLoad( const QModelIndex & iData, QString & texformat, GLuint & width, GL QModelIndex iPixelData = nif->getIndex( iData, "Pixel Data" ); if ( iPixelData.isValid() ) { - QModelIndex iFaceData = iPixelData.child( 0, 0 ); - - if ( iFaceData.isValid() ) { - if ( QByteArray * pdata = nif->get( iFaceData.child( 0, 0 ) ) ) { - buf.setData( *pdata ); - buf.open( QIODevice::ReadOnly ); - buf.seek( 0 ); - } + if ( QByteArray * pdata = nif->get( iPixelData.child(0, 0) ) ) { + buf.setData( *pdata ); + buf.open( QIODevice::ReadOnly ); + buf.seek( 0 ); } } @@ -1155,13 +1151,13 @@ bool texLoad( const QModelIndex & iData, QString & texformat, GLuint & width, GL hdr.ddsPixelFormat.dwFourCC = FOURCC_DXT1; ok = ( 0 != texLoadDXT( hdr, (const unsigned char *)buf.data().data(), buf.size() ) ); break; - case 5: //PX_FMT_DXT5 - texformat += " (DXT5)"; - hdr.ddsPixelFormat.dwFourCC = FOURCC_DXT5; + case 5: //PX_FMT_DXT3 + texformat += " (DXT3)"; + hdr.ddsPixelFormat.dwFourCC = FOURCC_DXT3; ok = ( 0 != texLoadDXT( hdr, (const unsigned char *)buf.data().data(), buf.size() ) ); break; - case 6: //PX_FMT_DXT5_ALT - texformat += " (DXT5ALT)"; + case 6: //PX_FMT_DXT5 + texformat += " (DXT5)"; hdr.ddsPixelFormat.dwFourCC = FOURCC_DXT5; ok = ( 0 != texLoadDXT( hdr, (const unsigned char *)buf.data().data(), buf.size() ) ); break; @@ -1189,7 +1185,7 @@ GLuint texLoadNIF( QIODevice & f, QString & texformat ) QPersistentModelIndex iRoot; for ( const auto l : pix.getRootLinks() ) { - QModelIndex iData = pix.getBlock( l, "ATextureRenderData" ); + QModelIndex iData = pix.getBlock( l, "NiPixelFormat" ); if ( !iData.isValid() || iData == QModelIndex() ) throw QString( "this is not a normal .nif file; there should be only pixel data as root blocks" ); @@ -1343,14 +1339,10 @@ bool texSaveDDS( const QModelIndex & index, const QString & filepath, const GLui QModelIndex iPixelData = nif->getIndex( index, "Pixel Data" ); if ( iPixelData.isValid() ) { - QModelIndex iFaceData = iPixelData.child( 0, 0 ); - - if ( iFaceData.isValid() ) { - if ( QByteArray * pdata = nif->get( iFaceData.child( 0, 0 ) ) ) { - buf.setData( *pdata ); - buf.open( QIODevice::ReadOnly ); - buf.seek( 0 ); - } + if ( QByteArray * pdata = nif->get( iPixelData.child( 0, 0 ) ) ) { + buf.setData( *pdata ); + buf.open( QIODevice::ReadOnly ); + buf.seek( 0 ); } } @@ -1476,6 +1468,7 @@ bool texSaveDDS( const QModelIndex & index, const QString & filepath, const GLui fourcc = FOURCC_DXT1; break; case 5: + fourcc = FOURCC_DXT3; case 6: fourcc = FOURCC_DXT5; break; @@ -1737,38 +1730,27 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) nif->set( iData, "Green Mask", pix.get( iPixData, "Green Mask" ) ); nif->set( iData, "Blue Mask", pix.get( iPixData, "Blue Mask" ) ); nif->set( iData, "Alpha Mask", pix.get( iPixData, "Alpha Mask" ) ); - nif->set( iData, "Bits Per Pixel", pix.get( iPixData, "Bits Per Pixel" ) ); - - QModelIndex unknownSrc; - QModelIndex unknownDest; - - // 2 sets of unknown bytes - unknownSrc = pix.getIndex( iPixData, "Unknown 3 Bytes" ); - unknownDest = nif->getIndex( iData, "Unknown 3 Bytes" ); - - for ( int i = 0; i < pix.rowCount( unknownSrc ); i++ ) { - nif->set( unknownDest.child( i, 0 ), pix.get( unknownSrc.child( i, 0 ) ) ); - } + nif->set( iData, "Bits Per Pixel", pix.get( iPixData, "Bits Per Pixel" ) ); - unknownSrc = pix.getIndex( iPixData, "Unknown 8 Bytes" ); - unknownDest = nif->getIndex( iData, "Unknown 8 Bytes" ); + QModelIndex fastCompareSrc = pix.getIndex( iPixData, "Old Fast Compare" ); + QModelIndex fastCompareDest = nif->getIndex( iData, "Old Fast Compare" ); - for ( int i = 0; i < pix.rowCount( unknownSrc ); i++ ) { - nif->set( unknownDest.child( i, 0 ), pix.get( unknownSrc.child( i, 0 ) ) ); + for ( int i = 0; i < pix.rowCount( fastCompareSrc ); i++ ) { + nif->set( fastCompareDest.child( i, 0 ), pix.get( fastCompareSrc.child( i, 0 ) ) ); } if ( nif->checkVersion( 0x0A010000, 0x0A020000 ) && pix.checkVersion( 0x0A010000, 0x0A020000 ) ) { - nif->set( iData, "Unknown Int", pix.get( iPixData, "Unknown Int" ) ); + nif->set( iData, "Tiling", pix.get( iPixData, "Tiling" ) ); } } else if ( nif->checkVersion( 0x14000004, 0 ) && pix.checkVersion( 0x14000004, 0 ) ) { - nif->set( iData, "Bits Per Pixel", pix.get( iPixData, "Bits Per Pixel" ) ); - nif->set( iData, "Unknown Int 2", pix.get( iPixData, "Unknown Int 2" ) ); - nif->set( iData, "Unknown Int 3", pix.get( iPixData, "Unknown Int 3" ) ); - nif->set( iData, "Flags", pix.get( iPixData, "Flags" ) ); - nif->set( iData, "Unknown Int 4", pix.get( iPixData, "Unknown Int 4" ) ); + nif->set( iData, "Bits Per Pixel", pix.get( iPixData, "Bits Per Pixel" ) ); + nif->set( iData, "Renderer Hint", pix.get( iPixData, "Renderer Hint" ) ); + nif->set( iData, "Extra Data", pix.get( iPixData, "Extra Data" ) ); + nif->set( iData, "Flags", pix.get( iPixData, "Flags" ) ); + nif->set( iData, "Tiling", pix.get( iPixData, "Tiling" ) ); if ( nif->checkVersion( 0x14030006, 0 ) && pix.checkVersion( 0x14030006, 0 ) ) { - nif->set( iData, "Unknown Byte 1", pix.get( iPixData, "Unknown Byte 1" ) ); + nif->set( iData, "sRGB Space", pix.get( iPixData, "sRGB Space" ) ); } QModelIndex srcChannels = pix.getIndex( iPixData, "Channels" ); @@ -1780,12 +1762,12 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) qDebug() << pix.get( srcChannels.child( i, 0 ), "Type" ); qDebug() << pix.get( srcChannels.child( i, 0 ), "Convention" ); qDebug() << pix.get( srcChannels.child( i, 0 ), "Bits Per Channel" ); - qDebug() << pix.get( srcChannels.child( i, 0 ), "Unknown Byte 1" ); + qDebug() << pix.get( srcChannels.child( i, 0 ), "Signed" ); nif->set( destChannels.child( i, 0 ), "Type", pix.get( srcChannels.child( i, 0 ), "Type" ) ); nif->set( destChannels.child( i, 0 ), "Convention", pix.get( srcChannels.child( i, 0 ), "Convention" ) ); nif->set( destChannels.child( i, 0 ), "Bits Per Channel", pix.get( srcChannels.child( i, 0 ), "Bits Per Channel" ) ); - nif->set( destChannels.child( i, 0 ), "Unknown Byte 1", pix.get( srcChannels.child( i, 0 ), "Unknown Byte 1" ) ); + nif->set( destChannels.child( i, 0 ), "Signed", pix.get( srcChannels.child( i, 0 ), "Signed" ) ); } nif->set( iData, "Num Faces", pix.get( iPixData, "Num Faces" ) ); @@ -1851,22 +1833,22 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) nif->set( iData, "Blue Mask", RGBA_INV_MASK[2] ); nif->set( iData, "Alpha Mask", RGBA_INV_MASK[3] ); - QModelIndex unknownEightBytes = nif->getIndex( iData, "Unknown 8 Bytes" ); + QModelIndex oldFastCompare = nif->getIndex( iData, "Old Fast Compare" ); for ( int i = 0; i < 8; i++ ) { - nif->set( unknownEightBytes.child( i, 0 ), unk8bytes32[i] ); + nif->set( oldFastCompare.child( i, 0 ), unk8bytes32[i] ); } } else if ( nif->checkVersion( 0x14000004, 0 ) ) { // set stuff - nif->set( iData, "Unknown Int 2", -1 ); // probably a link to something + nif->set( iData, "Extra Data", -1 ); nif->set( iData, "Flags", 1 ); QModelIndex destChannels = nif->getIndex( iData, "Channels" ); for ( int i = 0; i < 4; i++ ) { nif->set( destChannels.child( i, 0 ), "Type", i ); // red, green, blue, alpha nif->set( destChannels.child( i, 0 ), "Convention", 0 ); // fixed - nif->set( destChannels.child( i, 0 ), "Bits Per Channel", 8 ); - nif->set( destChannels.child( i, 0 ), "Unknown Byte 1", 0 ); + nif->set( destChannels.child( i, 0 ), "Bits Per Channel", 8 ); + nif->set( destChannels.child( i, 0 ), "Signed", 0 ); } } @@ -1910,10 +1892,8 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) QModelIndex iPixelData = nif->getIndex( iData, "Pixel Data" ); nif->updateArray( iPixelData ); - QModelIndex iFaceData = iPixelData.child( 0, 0 ); - nif->updateArray( iFaceData ); - nif->set( iFaceData, "Pixel Data", pixelData ); + nif->set( iPixelData, "Pixel Data", pixelData ); // return true once perfected //return false; @@ -1980,18 +1960,18 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) nif->set( iData, "Blue Mask", ddsHeader.ddsPixelFormat.dwBMask ); nif->set( iData, "Alpha Mask", ddsHeader.ddsPixelFormat.dwAMask ); - QModelIndex unknownEightBytes = nif->getIndex( iData, "Unknown 8 Bytes" ); + QModelIndex oldFastCompare = nif->getIndex( iData, "Old Fast Compare" ); for ( int i = 0; i < 8; i++ ) { if ( ddsHeader.ddsPixelFormat.dwBPP == 24 ) { - nif->set( unknownEightBytes.child( i, 0 ), unk8bytes24[i] ); + nif->set( oldFastCompare.child( i, 0 ), unk8bytes24[i] ); } else if ( ddsHeader.ddsPixelFormat.dwBPP == 32 ) { - nif->set( unknownEightBytes.child( i, 0 ), unk8bytes32[i] ); + nif->set( oldFastCompare.child( i, 0 ), unk8bytes32[i] ); } } } else if ( nif->checkVersion( 0x14000004, 0 ) ) { // set stuff - nif->set( iData, "Unknown Int 2", -1 ); // probably a link to something + nif->set( iData, "Extra Data", -1 ); nif->set( iData, "Flags", 1 ); QModelIndex destChannels = nif->getIndex( iData, "Channels" ); @@ -2010,7 +1990,7 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) } nif->set( destChannels.child( i, 0 ), "Bits Per Channel", 0 ); - nif->set( destChannels.child( i, 0 ), "Unknown Byte 1", 1 ); + nif->set( destChannels.child( i, 0 ), "Signed", 1 ); } } else { nif->set( iData, "Bits Per Pixel", ddsHeader.ddsPixelFormat.dwBPP ); @@ -2031,20 +2011,20 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) for ( int i = 0; i < 3; i++ ) { nif->set( destChannels.child( i, 0 ), "Convention", 0 ); // fixed - nif->set( destChannels.child( i, 0 ), "Bits Per Channel", 8 ); - nif->set( destChannels.child( i, 0 ), "Unknown Byte 1", 0 ); + nif->set( destChannels.child( i, 0 ), "Bits Per Channel", 8 ); + nif->set( destChannels.child( i, 0 ), "Signed", 0 ); } if ( ddsHeader.ddsPixelFormat.dwBPP == 32 ) { nif->set( destChannels.child( 3, 0 ), "Type", 3 ); // alpha nif->set( destChannels.child( 3, 0 ), "Convention", 0 ); // fixed - nif->set( destChannels.child( 3, 0 ), "Bits Per Channel", 8 ); - nif->set( destChannels.child( 3, 0 ), "Unknown Byte 1", 0 ); + nif->set( destChannels.child( 3, 0 ), "Bits Per Channel", 8 ); + nif->set( destChannels.child( 3, 0 ), "Signed", 0 ); } else if ( ddsHeader.ddsPixelFormat.dwBPP == 24 ) { nif->set( destChannels.child( 3, 0 ), "Type", 19 ); // empty nif->set( destChannels.child( 3, 0 ), "Convention", 5 ); // empty - nif->set( destChannels.child( 3, 0 ), "Bits Per Channel", 0 ); - nif->set( destChannels.child( 3, 0 ), "Unknown Byte 1", 0 ); + nif->set( destChannels.child( 3, 0 ), "Bits Per Channel", 0 ); + nif->set( destChannels.child( 3, 0 ), "Signed", 0 ); } } } @@ -2088,8 +2068,6 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) QModelIndex iPixelData = nif->getIndex( iData, "Pixel Data" ); nif->updateArray( iPixelData ); - QModelIndex iFaceData = iPixelData.child( 0, 0 ); - nif->updateArray( iFaceData ); f.seek( 4 + ddsHeader.dwSize ); //qDebug() << "Reading from " << f.pos(); @@ -2102,7 +2080,7 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) return false; } - nif->set( iFaceData, "Pixel Data", ddsData ); + nif->set( iPixelData, "Pixel Data", ddsData ); /* QByteArray result = nif->get( iFaceData, "Pixel Data" ); diff --git a/src/spells/animation.cpp b/src/spells/animation.cpp index 9d25a9aea..e585de754 100644 --- a/src/spells/animation.cpp +++ b/src/spells/animation.cpp @@ -60,10 +60,7 @@ class spAttachKf final : public Spell if ( !iSeq.isValid() ) throw QString( Spell::tr( "this is not a normal .kf file; there should be only NiControllerSequences as root blocks" ) ); - QString rootName = kf.get( iSeq, "Target Name" ); - - if ( rootName.isEmpty() ) - rootName = kf.get( iSeq, "Text Keys Name" ); // 10.0.1.0 + QString rootName = kf.get( iSeq, "Accum Root Name" ); QModelIndex ir = findRootTarget( nif, rootName ); diff --git a/src/spells/blocks.cpp b/src/spells/blocks.cpp index a13297ba5..2f6a409e6 100644 --- a/src/spells/blocks.cpp +++ b/src/spells/blocks.cpp @@ -377,8 +377,9 @@ class spAttachLight final : public Spell addLink( nif, iParent, "Effects", nif->getBlockNumber( iLight ) ); if ( nif->checkVersion( 0, 0x04000002 ) ) { - nif->set( iLight, "Num Affected Node List Pointers", 1 ); - nif->updateArray( iLight, "Affected Node List Pointers" ); + nif->set( iLight, "Num Affected Nodes", 1 ); + nif->updateArray( iLight, "Affected Nodes" ); + nif->updateArray( iLight, "Affected Node Pointers" ); } if ( act->text() == "NiTextureEffect" ) { diff --git a/src/spells/light.cpp b/src/spells/light.cpp index 1b2028e3f..7c4ba6934 100644 --- a/src/spells/light.cpp +++ b/src/spells/light.cpp @@ -94,7 +94,8 @@ class spLightEdit final : public Spell le->add( new NifFloatEdit( nif, nif->getIndex( iLight, "Quadratic Attenuation" ) ) ); le->popLayout(); le->pushLayout( new QHBoxLayout(), "Spot Light Parameters" ); - le->add( new NifFloatEdit( nif, nif->getIndex( iLight, "Cutoff Angle" ), 0, 90 ) ); + le->add( new NifFloatEdit( nif, nif->getIndex( iLight, "Outer Spot Angle" ), 0, 90 ) ); + le->add( new NifFloatEdit( nif, nif->getIndex( iLight, "Inner Spot Angle" ), 0, 90 ) ); le->add( new NifFloatEdit( nif, nif->getIndex( iLight, "Exponent" ), 0, 128 ) ); le->popLayout(); le->show(); diff --git a/src/spells/texture.cpp b/src/spells/texture.cpp index 5505743cf..6a9312a70 100644 --- a/src/spells/texture.cpp +++ b/src/spells/texture.cpp @@ -308,7 +308,7 @@ QModelIndex addTexture( NifModel * nif, const QModelIndex & index, const QString nif->set( iSrcTex, "Use Mipmaps", 2 ); nif->set( iSrcTex, "Alpha Format", 3 ); nif->set( iSrcTex, "Unknown Byte", 1 ); - nif->set( iSrcTex, "Unknown Byte 2", 1 ); + nif->set( iSrcTex, "Is Static", 1 ); nif->set( iSrcTex, "Use External", 1 ); spChooseTexture * chooser = new spChooseTexture(); @@ -855,7 +855,7 @@ class spExportTexture final : public Spell if ( iData.isValid() ) { return true; } - } else if ( nif->inherits( iBlock, "ATextureRenderData" ) ) { + } else if ( nif->inherits( iBlock, "NiPixelFormat" ) ) { int thisBlockNumber = nif->getBlockNumber( index ); QModelIndex iParent = nif->getBlock( nif->getParent( thisBlockNumber ) ); @@ -895,7 +895,7 @@ class spExportTexture final : public Spell } return index; - } else if ( nif->inherits( iBlock, "ATextureRenderData" ) ) { + } else if ( nif->inherits( iBlock, "NiPixelFormat" ) ) { TexCache * tex = new TexCache(); tex->setNifFolder( nif->getFolder() ); QString file = nif->getFolder(); diff --git a/src/xml/nifxml.cpp b/src/xml/nifxml.cpp index 3cf911213..a6fa5d713 100644 --- a/src/xml/nifxml.cpp +++ b/src/xml/nifxml.cpp @@ -289,7 +289,7 @@ class NifXmlHandler final : public QXmlDefaultHandler bool isMixin = false; if ( isCompound ) { static const QVector mixinTypes { - "HavokColFilter", + "HavokFilter", "HavokMaterial", "RagdollDescriptor", "LimitedHingeDescriptor", From 31c33577ccbd4af66f3f9d5b130c2bc8e63958ba Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sat, 23 Sep 2017 09:51:06 -0400 Subject: [PATCH 068/152] [NiMesh] Initial rendering support Uncommented test code for rendering NiMesh (replaced NiTriShape, etc.). Also had to update it some to get it working with skinned meshes. Some games still do not render correctly but most do. The test code that was already in place appears to be quite slow, so NIFs using NiMesh will have a pretty long delay on load. --- src/gl/glmesh.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/gl/glmesh.cpp b/src/gl/glmesh.cpp index eaf850ca5..8acca7902 100644 --- a/src/gl/glmesh.cpp +++ b/src/gl/glmesh.cpp @@ -208,7 +208,7 @@ void Mesh::update( const NifModel * nif, const QModelIndex & index ) if ( iBlock == index ) { // NiMesh presents a problem because we are almost guaranteed to have multiple "data" blocks // for eg. vertices, indices, normals, texture data etc. -#ifndef QT_NO_DEBUG +//#ifndef QT_NO_DEBUG if ( nif->checkVersion( 0x14050000, 0 ) && nif->inherits( iBlock, "NiMesh" ) ) { qDebug() << nif->get( iBlock, "Num Submeshes" ) << " submeshes"; @@ -224,7 +224,7 @@ void Mesh::update( const NifModel * nif, const QModelIndex & index ) return; } -#endif +//#endif for ( const auto link : nif->getChildLinks( id() ) ) { QModelIndex iChild = nif->getBlock( link ); @@ -298,7 +298,7 @@ void Mesh::transform() // update for NiMesh if ( nif->checkVersion( 0x14050000, 0 ) && nif->inherits( iBlock, "NiMesh" ) ) { -#ifndef QT_NO_DEBUG +//#ifndef QT_NO_DEBUG // do stuff qDebug() << "Entering NiMesh decoding..."; // mesh primitive type @@ -430,10 +430,14 @@ void Mesh::transform() if ( NifValue::enumOptionName( "ComponentFormat", (typeList[k] + 1 ) ) == "F_FLOAT32_3" ) { Vector3 tempVect3( values[0].toFloat(), values[1].toFloat(), values[2].toFloat() ); - if ( compType == "POSITION" ) { + if ( compType == "POSITION" || compType == "POSITION_BP" ) { verts.append( tempVect3 ); - } else if ( compType == "NORMAL" ) { + } else if ( compType == "NORMAL" || compType == "NORMAL_BP" ) { norms.append( tempVect3 ); + } else if ( compType == "TANGENT" || compType == "TANGENT_BP" ) { + tangents.append( tempVect3 ); + } else if ( compType == "BINORMAL" || compType == "BINORMAL_BP" ) { + bitangents.append( tempVect3 ); } } else if ( compType == "INDEX" ) { indices.append( values[0].toCount() ); @@ -459,7 +463,7 @@ void Mesh::transform() } } -#endif +//#endif } else { verts = nif->getArray( iData, "Vertices" ); From 555880afd65fd93042efa3becf978b93990e75d8 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sat, 23 Sep 2017 09:51:41 -0400 Subject: [PATCH 069/152] Disable Unknown Property warning This warning was not really meant for normal users and endlessly confuses them. --- src/gl/glproperty.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gl/glproperty.cpp b/src/gl/glproperty.cpp index 863828ed4..9297a0307 100644 --- a/src/gl/glproperty.cpp +++ b/src/gl/glproperty.cpp @@ -85,12 +85,14 @@ Property * Property::create( Scene * scene, const NifModel * nif, const QModelIn } else if ( nif->isNiBlock( index, "BSShaderPPLightingProperty" ) ) { property = new BSShaderLightingProperty( scene, index ); } else if ( index.isValid() ) { +#ifndef QT_NO_DEBUG NifItem * item = static_cast( index.internalPointer() ); if ( item ) qCWarning( nsNif ) << tr( "Unknown property: %1" ).arg( item->name() ); else qCWarning( nsNif ) << tr( "Unknown property: I can't determine its name" ); +#endif } if ( property ) From 8d75c96410a955a02bf0376abb7530ba5cabd8fc Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Wed, 4 Oct 2017 21:20:15 -0400 Subject: [PATCH 070/152] [GL] Accumulate scene bounds for NiMesh Now with experimental NiMesh rendering, the NiMesh bounds need to be accumulated for the scene. --- src/gl/glnode.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/gl/glnode.cpp b/src/gl/glnode.cpp index 990e9d734..a6c910012 100644 --- a/src/gl/glnode.cpp +++ b/src/gl/glnode.cpp @@ -1917,6 +1917,15 @@ BoundSphere Node::bounds() const boundsphere |= BoundSphere( trans, rad.length() ); } + if ( nif->getBlockType( iBlock ) == "NiMesh" ) { + auto iBound = nif->getIndex( iBlock, "Bound" ); + if ( iBound.isValid() ) { + auto center = nif->get( iBound, "Center" ); + auto radius = nif->get( iBound, "Radius" ); + boundsphere |= BoundSphere( center, radius ); + } + } + // BSBound collision bounding box QModelIndex iExtraDataList = nif->getIndex( iBlock, "Extra Data List" ); From 7f57f23877eccbbbfa2a01798a343a474a4c2300 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Wed, 4 Oct 2017 21:21:30 -0400 Subject: [PATCH 071/152] [GL] Support non-Bethesda texture paths again Redo the texture path cleaning meant only for Bethesda meshes so that older NIFs can still find their textures. --- src/gl/gltex.cpp | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/gl/gltex.cpp b/src/gl/gltex.cpp index 6a420ddf7..0bccf5f3e 100644 --- a/src/gl/gltex.cpp +++ b/src/gl/gltex.cpp @@ -162,20 +162,6 @@ QString TexCache::find( const QString & file, const QString & nifdir, QByteArray QString filename = QDir::toNativeSeparators( file ); - if ( !filename.startsWith( "textures" ) ) { - QRegularExpression re( "textures[\\\\/]", QRegularExpression::CaseInsensitiveOption ); - int texIdx = filename.indexOf( re ); - if ( texIdx > 0 ) { - filename.remove( 0, texIdx ); - } else { - while ( filename.startsWith( "/" ) || filename.startsWith( "\\" ) ) - filename.remove( 0, 1 ); - - if ( !filename.startsWith( "textures", Qt::CaseInsensitive ) && !filename.startsWith( "shaders", Qt::CaseInsensitive ) ) - filename.prepend( "textures\\" ); - } - } - QStringList extensions; extensions << ".dds"; bool replaceExt = false; @@ -253,6 +239,23 @@ QString TexCache::find( const QString & file, const QString & nifdir, QByteArray } } + // For Skyrim and FO4 which occasionally leave the textures off + if ( !filename.startsWith( "textures" ) ) { + QRegularExpression re( "textures[\\\\/]", QRegularExpression::CaseInsensitiveOption ); + int texIdx = filename.indexOf( re ); + if ( texIdx > 0 ) { + filename.remove( 0, texIdx ); + } else { + while ( filename.startsWith( "/" ) || filename.startsWith( "\\" ) ) + filename.remove( 0, 1 ); + + if ( !filename.startsWith( "textures", Qt::CaseInsensitive ) && !filename.startsWith( "shaders", Qt::CaseInsensitive ) ) + filename.prepend( "textures\\" ); + } + + return find( filename, nifdir, data ); + } + if ( !replaceExt ) break; From d910f6e37a669350bd6c9bbd610b2000a612bfe1 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Wed, 4 Oct 2017 22:23:19 -0400 Subject: [PATCH 072/152] [Spells] Export Template support for SSE and FO4 Also fix isApplicable for Edit UV to not apply to child rows of the block. --- src/spells/texture.cpp | 46 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/src/spells/texture.cpp b/src/spells/texture.cpp index 6a9312a70..44c0acb28 100644 --- a/src/spells/texture.cpp +++ b/src/spells/texture.cpp @@ -262,7 +262,10 @@ class spEditTexCoords final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - return nif->inherits( index, "NiTriBasedGeom" ) || nif->inherits( index, "BSTriShape" ); + auto iUVs = getUV( nif, index ); + auto iTriData = nif->getIndex( index, "Num Triangles" ); + return (iUVs.isValid() || iTriData.isValid()) + && (nif->inherits( index, "NiTriBasedGeom" ) || nif->inherits( index, "BSTriShape" )); } QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final @@ -525,14 +528,16 @@ class spTextureTemplate final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { QModelIndex iUVs = getUV( nif, index ); - return iUVs.isValid() && nif->rowCount( iUVs ) >= 1; + auto iTriData = nif->getIndex( index, "Num Triangles" ); + bool bstri = nif->getUserVersion2() >= 100 && nif->inherits( index, "BSTriShape" ) && iTriData.isValid(); + return (iUVs.isValid() && nif->rowCount( iUVs ) >= 1) || bstri; } QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final { QModelIndex iUVs = getUV( nif, index ); - if ( nif->rowCount( iUVs ) <= 0 ) + if ( nif->rowCount( iUVs ) <= 0 && nif->getUserVersion2() < 100 ) return index; // fire up a dialog to set the user parameters @@ -617,9 +622,40 @@ class spTextureTemplate final : public Spell // get the selected coord set QModelIndex iSet = iUVs.child( set->currentIndex(), 0 ); - QVector uv = nif->getArray( iSet ); + QVector uv; QVector tri; + if ( nif->getUserVersion2() >= 100 ) { + QModelIndex iVertData; + auto vf = nif->get( index, "VF" ); + if ( (vf & 0x400) && nif->getUserVersion2() == 100 ) { + // Skinned SSE + auto skinID = nif->getLink( nif->getIndex( index, "Skin" ) ); + auto partID = nif->getLink( nif->getBlock( skinID, "NiSkinInstance" ), "Skin Partition" ); + auto iPartBlock = nif->getBlock( partID, "NiSkinPartition" ); + if ( !iPartBlock.isValid() ) + return index; + + iVertData = nif->getIndex( iPartBlock, "Vertex Data" ); + + // Get triangles from all partitions + auto numParts = nif->get( iPartBlock, "Num Skin Partition Blocks" ); + auto iParts = nif->getIndex( iPartBlock, "Partition" ); + for ( int i = 0; i < numParts; i++ ) + tri << nif->getArray( iParts.child( i, 0 ), "Triangles" ); + + } else { + iVertData = nif->getIndex( index, "Vertex Data" ); + tri = nif->getArray( index, "Triangles" ); + } + + for ( int i = 0; i < nif->rowCount( iVertData ); i++ ) + uv << nif->get( nif->index( i, 0, iVertData ), "UV" ); + + } else { + uv = nif->getArray( iSet ); + } + // get the triangles QModelIndex iData = getData( nif, index ); QModelIndex iPoints = nif->getIndex( iData, "Points" ); @@ -631,7 +667,7 @@ class spTextureTemplate final : public Spell strips.append( nif->getArray( iPoints.child( r, 0 ) ) ); tri = triangulate( strips ); - } else { + } else if ( nif->getUserVersion2() < 100 ) { tri = nif->getArray( nif->getIndex( getData( nif, index ), "Triangles" ) ); } From 5c2c9d44e366e0472e03e5cf06666c445fe9de72 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Thu, 5 Oct 2017 21:06:20 -0400 Subject: [PATCH 073/152] [Spells] Transform > Apply fixes, support for BSTriShape Transform > Apply never worked correctly when a mesh had Tangents and Bitangents and the Transform had a non-identity rotation. Tangents/Bitangents were not transformed so you would need to run Update Tangent Space to correct this. Also added support for BSTriShape. The location of skinned Vertex Data being in the partition for SSE does not factor in since Apply should not be run on skinned geometry anyway. --- src/spells/transform.cpp | 86 ++++++++++++++++++++++++++++++++++------ 1 file changed, 73 insertions(+), 13 deletions(-) diff --git a/src/spells/transform.cpp b/src/spells/transform.cpp index ce58724f7..bb3204d8f 100644 --- a/src/spells/transform.cpp +++ b/src/spells/transform.cpp @@ -93,15 +93,17 @@ static char const * transform_xpm[] = { bool spApplyTransformation::isApplicable( const NifModel * nif, const QModelIndex & index ) { - return nif->itemType( index ) == "NiBlock" && ( nif->inherits( nif->itemName( index ), "NiNode" ) - || nif->itemName( index ) == "NiTriShape" - || nif->itemName( index ) == "BSLODTriShape" - || nif->itemName( index ) == "NiTriStrips" ); + return nif->itemType( index ) == "NiBlock" && + ( nif->inherits( nif->itemName( index ), "NiNode" ) + || nif->inherits( nif->itemName( index ), "NiTriBasedGeom" ) + || nif->inherits( nif->itemName( index ), "BSTriShape" )); } QModelIndex spApplyTransformation::cast( NifModel * nif, const QModelIndex & index ) { - if ( ( nif->getLink( index, "Controller" ) != -1 || nif->getLink( index, "Skin Instance" ) != -1 ) ) + if ( nif->getLink( index, "Controller" ) != -1 + || nif->getLink( index, "Skin Instance" ) != -1 + || nif->getLink( index, "Skin" ) != -1 ) if ( QMessageBox::question( 0, Spell::tr( "Apply Transformation" ), Spell::tr( "On animated and or skinned nodes Apply Transformation most likely won't work the way you expected it." ), Spell::tr( "Try anyway" ), @@ -131,7 +133,7 @@ QModelIndex spApplyTransformation::cast( NifModel * nif, const QModelIndex & ind tp = Transform(); tp.writeBack( nif, index ); } - } else { + } else if ( nif->inherits( nif->itemName( index ), "NiTriBasedGeom" ) ) { QModelIndex iData; if ( nif->itemName( index ) == "NiTriShape" || nif->itemName( index ) == "BSLODTriShape" ) @@ -151,15 +153,22 @@ QModelIndex spApplyTransformation::cast( NifModel * nif, const QModelIndex & ind nif->setArray( iVertices, a ); - QModelIndex iNormals = nif->getIndex( iData, "Normals" ); + auto transformBTN = [nif, iData, &t]( QString str ) + { + QModelIndex idx = nif->getIndex( iData, str ); + if ( idx.isValid() ) { + auto a = nif->getArray( idx ); + for ( int n = 0; n < a.size(); n++ ) + a[n] = t.rotation * a[n]; - if ( iNormals.isValid() ) { - a = nif->getArray( iNormals ); - - for ( int n = 0; n < nif->rowCount( iNormals ); n++ ) - a[n] = t.rotation * a[n]; + nif->setArray( idx, a ); + } + }; - nif->setArray( iNormals, a ); + if ( !(t.rotation == Matrix()) ) { + transformBTN( "Normals" ); + transformBTN( "Tangents" ); + transformBTN( "Bitangents" ); } } @@ -176,6 +185,57 @@ QModelIndex spApplyTransformation::cast( NifModel * nif, const QModelIndex & ind t = Transform(); t.writeBack( nif, index ); } + } else if ( nif->inherits( nif->itemName( index ), "BSTriShape" ) ) { + // Should not be used on skinned anyway so do not bother with SSE skinned geometry support + QModelIndex iVertData = nif->getIndex( index, "Vertex Data" ); + if ( !iVertData.isValid() ) + return index; + + Transform t( nif, index ); + + // Update Bounding Sphere + auto iBound = nif->getIndex( index, "Bounding Sphere" ); + QModelIndex iCenter = nif->getIndex( iBound, "Center" ); + if ( iCenter.isValid() ) + nif->set( iCenter, t * nif->get( iCenter ) ); + + QModelIndex iRadius = nif->getIndex( iBound, "Radius" ); + if ( iRadius.isValid() ) + nif->set( iRadius, t.scale * nif->get( iRadius ) ); + + nif->setState( BaseModel::Processing ); + for ( int i = 0; i < nif->rowCount( iVertData ); i++ ) { + auto iVert = iVertData.child( i, 0 ); + + auto vertex = t * nif->get( iVert, "Vertex" ); + if ( !nif->set( iVert, "Vertex", vertex ) ) + nif->set( iVert, "Vertex", vertex ); + + // Transform BTN if applicable + if ( !(t.rotation == Matrix()) ) { + auto normal = nif->get( iVert, "Normal" ); + nif->set( iVert, "Normal", t.rotation * normal ); + + auto tangent = nif->get( iVert, "Tangent" ); + nif->set( iVert, "Tangent", t.rotation * tangent ); + + // Unpack, Transform, Pack Bitangent + auto bitX = nif->getValue( nif->getIndex( iVert, "Bitangent X" ) ).toFloat(); + auto bitYi = nif->getValue( nif->getIndex( iVert, "Bitangent Y" ) ).toCount(); + auto bitZi = nif->getValue( nif->getIndex( iVert, "Bitangent Z" ) ).toCount(); + + auto bit = t.rotation * Vector3( bitX, (bitYi / 255.0) * 2.0 - 1.0, (bitZi / 255.0) * 2.0 - 1.0 ); + + nif->set( iVert, "Bitangent X", bit[0] ); + nif->set( iVert, "Bitangent Y", round( ((bit[1] + 1.0) / 2.0) * 255.0 ) ); + nif->set( iVert, "Bitangent Z", round( ((bit[2] + 1.0) / 2.0) * 255.0 ) ); + } + } + + nif->resetState(); + + t = Transform(); + t.writeBack( nif, index ); } return index; From 8c217ac9323b7511c12c4e4a273807b323154506 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Thu, 5 Oct 2017 21:47:20 -0400 Subject: [PATCH 074/152] [Spells] Face/Smooth Normals support for SSE Needed special behavior for SSE Skinned meshes, which have the geometry in the partition. --- src/spells/normals.cpp | 82 ++++++++++++++++++++++++++++-------------- 1 file changed, 56 insertions(+), 26 deletions(-) diff --git a/src/spells/normals.cpp b/src/spells/normals.cpp index 5ce836e29..d2bfdd0c6 100644 --- a/src/spells/normals.cpp +++ b/src/spells/normals.cpp @@ -33,8 +33,19 @@ class spFaceNormals final : public Spell if ( nif->isNiBlock( iData, { "NiTriShapeData", "NiTriStripsData" } ) ) return iData; - if ( nif->isNiBlock( index, { "BSTriShape", "BSMeshLODTriShape", "BSSubIndexTriShape" } ) ) + if ( nif->isNiBlock( index, { "BSTriShape", "BSMeshLODTriShape", "BSSubIndexTriShape" } ) ) { + auto vf = nif->get( index, "VF" ); + if ( (vf & 0x400) && nif->getUserVersion2() == 100 ) { + // Skinned SSE + auto skinID = nif->getLink( nif->getIndex( index, "Skin" ) ); + auto partID = nif->getLink( nif->getBlock( skinID, "NiSkinInstance" ), "Skin Partition" ); + auto iPartBlock = nif->getBlock( partID, "NiSkinPartition" ); + if ( iPartBlock.isValid() ) + return nif->getIndex( iPartBlock, "Vertex Data" ); + } + return nif->getIndex( index, "Vertex Data" ); + } return QModelIndex(); } @@ -65,7 +76,7 @@ class spFaceNormals final : public Spell } }; - if ( nif->getUserVersion2() < 130 ) { + if ( nif->getUserVersion2() < 100 ) { QVector verts = nif->getArray( iData, "Vertices" ); QVector triangles; QModelIndex iPoints = nif->getIndex( iData, "Points" ); @@ -90,10 +101,27 @@ class spFaceNormals final : public Spell nif->updateArray( iData, "Normals" ); nif->setArray( iData, "Normals", norms ); } else { - int numVerts = nif->get( index, "Num Vertices" ); + QVector triangles; + int numVerts; + auto vf = nif->get( index, "VF" ); + if ( !((vf & 0x400) && nif->getUserVersion2() == 100) ) { + numVerts = nif->get( index, "Num Vertices" ); + triangles = nif->getArray( index, "Triangles" ); + } else { + // Skinned SSE + auto iPart = iData.parent(); + numVerts = nif->get( iPart, "Data Size" ) / nif->get( iPart, "Vertex Size" ); + + // Get triangles from all partitions + auto numParts = nif->get( iPart, "Num Skin Partition Blocks" ); + auto iParts = nif->getIndex( iPart, "Partition" ); + for ( int i = 0; i < numParts; i++ ) + triangles << nif->getArray( iParts.child( i, 0 ), "Triangles" ); + } + QVector verts; + verts.reserve( numVerts ); QVector norms( numVerts ); - QVector triangles = nif->getArray( index, "Triangles" ); for ( int i = 0; i < numVerts; i++ ) { auto idx = nif->index( i, 0, iData ); @@ -104,16 +132,11 @@ class spFaceNormals final : public Spell faceNormals( verts, triangles, norms ); // Pause updates between model/view - nif->setEmitChanges( false ); + nif->setState( BaseModel::Processing ); for ( int i = 0; i < numVerts; i++ ) { - // Unpause updates if last - if ( i == numVerts - 1 ) - nif->setEmitChanges( true ); - - auto idx = nif->index( i, 0, iData ); - - nif->set( idx, "Normal", *static_cast(&norms[i]) ); + nif->set( nif->index( i, 0, iData ), "Normal", norms[i] ); } + nif->resetState(); } return index; @@ -171,11 +194,25 @@ class spSmoothNormals final : public Spell QVector verts; QVector norms; - if ( nif->getUserVersion2() < 130 ) { + int numVerts; + + if ( nif->getUserVersion2() < 100 ) { verts = nif->getArray( iData, "Vertices" ); norms = nif->getArray( iData, "Normals" ); } else { - int numVerts = nif->get( index, "Num Vertices" ); + auto vf = nif->get( index, "VF" ); + if ( !((vf & 0x400) && nif->getUserVersion2() == 100) ) { + numVerts = nif->get( index, "Num Vertices" ); + } else { + // Skinned SSE + // "Num Vertices" does not exist in the partition + auto iPart = iData.parent(); + numVerts = nif->get( iPart, "Data Size" ) / nif->get( iPart, "Vertex Size" ); + } + + verts.reserve( numVerts ); + norms.reserve( numVerts ); + for ( int i = 0; i < numVerts; i++ ) { auto idx = nif->index( i, 0, iData ); @@ -251,21 +288,14 @@ class spSmoothNormals final : public Spell for ( int i = 0; i < verts.count(); i++ ) snorms[i].normalize(); - if ( nif->getUserVersion2() < 130 ) { + if ( nif->getUserVersion2() < 100 ) { nif->setArray( iData, "Normals", snorms ); } else { - int numVerts = nif->get( index, "Num Vertices" ); // Pause updates between model/view - nif->setEmitChanges( false ); - for ( int i = 0; i < numVerts; i++ ) { - // Unpause updates if last - if ( i == numVerts - 1 ) - nif->setEmitChanges( true ); - - auto idx = nif->index( i, 0, iData ); - - nif->set( idx, "Normal", *static_cast(&snorms[i]) ); - } + nif->setState( BaseModel::Processing ); + for ( int i = 0; i < numVerts; i++ ) + nif->set( nif->index( i, 0, iData ), "Normal", snorms[i] ); + nif->resetState(); } From b6514e4b690dcf5bd81dc42387e09a36ffe09337 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Fri, 6 Oct 2017 00:16:07 -0400 Subject: [PATCH 075/152] [Spells] Update Tangents fixes, support for SSE Tangents were never updated for FO4, only Bitangents due to an issue with set. Added support for SSE, incl. skinned meshes where the geometry is on the partition. --- src/spells/tangentspace.cpp | 65 ++++++++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 15 deletions(-) diff --git a/src/spells/tangentspace.cpp b/src/spells/tangentspace.cpp index fad1e0ca3..e26b16a12 100644 --- a/src/spells/tangentspace.cpp +++ b/src/spells/tangentspace.cpp @@ -41,22 +41,42 @@ bool spTangentSpace::isApplicable( const NifModel * nif, const QModelIndex & ind QModelIndex spTangentSpace::cast( NifModel * nif, const QModelIndex & iBlock ) { QPersistentModelIndex iShape = iBlock; - QModelIndex iData; - if ( nif->getUserVersion2() < 130 ) + QModelIndex iPartBlock; + if ( nif->getUserVersion2() < 100 ) { iData = nif->getBlock( nif->getLink( iShape, "Data" ) ); - else - iData = nif->getIndex( iShape, "Vertex Data" ); + } else { + auto vf = nif->get( iShape, "VF" ); + if ( (vf & 0x400) && nif->getUserVersion2() == 100 ) { + // Skinned SSE + auto skinID = nif->getLink( nif->getIndex( iShape, "Skin" ) ); + auto partID = nif->getLink( nif->getBlock( skinID, "NiSkinInstance" ), "Skin Partition" ); + iPartBlock = nif->getBlock( partID, "NiSkinPartition" ); + if ( iPartBlock.isValid() ) + iData = nif->getIndex( iPartBlock, "Vertex Data" ); + } else { + iData = nif->getIndex( iShape, "Vertex Data" ); + } + } QVector verts; QVector norms; QVector texco; - if ( nif->getUserVersion2() < 130 ) { + if ( nif->getUserVersion2() < 100 ) { verts = nif->getArray( iData, "Vertices" ); norms = nif->getArray( iData, "Normals" ); } else { - int numVerts = nif->get( iShape, "Num Vertices" ); + int numVerts; + // "Num Vertices" does not exist in the partition + if ( iPartBlock.isValid() ) + numVerts = nif->get( iPartBlock, "Data Size" ) / nif->get( iPartBlock, "Vertex Size" ); + else + numVerts = nif->get( iShape, "Num Vertices" ); + + verts.reserve( numVerts ); + norms.reserve( numVerts ); + texco.reserve( numVerts ); for ( int i = 0; i < numVerts; i++ ) { auto idx = nif->index( i, 0, iData ); @@ -70,7 +90,7 @@ QModelIndex spTangentSpace::cast( NifModel * nif, const QModelIndex & iBlock ) int numUVSets = nif->get( iData, "Num UV Sets" ); int tspaceFlags = nif->get( iData, "TSpace Flag" ); - if ( nif->getUserVersion2() < 130 ) { + if ( nif->getUserVersion2() < 100 ) { QModelIndex iTexCo = nif->getIndex( iData, "UV Sets" ); if ( !iTexCo.isValid() ) @@ -91,10 +111,18 @@ QModelIndex spTangentSpace::cast( NifModel * nif, const QModelIndex & iBlock ) strips.append( nif->getArray( iPoints.child( r, 0 ) ) ); triangles = triangulate( strips ); - } else if ( nif->getUserVersion2() < 130 ) { + } else if ( nif->getUserVersion2() < 100 ) { triangles = nif->getArray( iData, "Triangles" ); - } else if ( nif->getUserVersion2() == 130 ) { - triangles = nif->getArray( iShape, "Triangles" ); + } else if ( nif->getUserVersion2() >= 100 ) { + if ( iPartBlock.isValid() ) { + // Get triangles from all partitions + auto numParts = nif->get( iPartBlock, "Num Skin Partition Blocks" ); + auto iParts = nif->getIndex( iPartBlock, "Partition" ); + for ( int i = 0; i < numParts; i++ ) + triangles << nif->getArray( iParts.child( i, 0 ), "Triangles" ); + } else { + triangles = nif->getArray( iShape, "Triangles" ); + } } if ( verts.isEmpty() || norms.count() != verts.count() || texco.count() != verts.count() || triangles.isEmpty() ) { @@ -248,7 +276,7 @@ QModelIndex spTangentSpace::cast( NifModel * nif, const QModelIndex & iBlock ) } nif->set( iTSpace, "Binary Data", QByteArray( (const char *)tan.data(), tan.count() * sizeof( Vector3 ) ) + QByteArray( (const char *)bin.data(), bin.count() * sizeof( Vector3 ) ) ); - } else if ( nif->getUserVersion2() < 130 ) { + } else if ( nif->getUserVersion2() < 100 ) { if ( tspaceFlags == 0 ) tspaceFlags = 0x10; @@ -260,13 +288,19 @@ QModelIndex spTangentSpace::cast( NifModel * nif, const QModelIndex & iBlock ) nif->updateArray( iTangents ); nif->setArray( iBinorms, bin ); nif->setArray( iTangents, tan ); - } else if ( nif->getUserVersion2() == 130 ) { - - int numVerts = nif->get( iShape, "Num Vertices" ); + } else if ( nif->getUserVersion2() >= 100 ) { + int numVerts; + // "Num Vertices" does not exist in the partition + if ( iPartBlock.isValid() ) + numVerts = nif->get( iPartBlock, "Data Size" ) / nif->get( iPartBlock, "Vertex Size" ); + else + numVerts = nif->get( iShape, "Num Vertices" ); + + nif->setState( BaseModel::Processing ); for ( int i = 0; i < numVerts; i++ ) { auto idx = nif->index( i, 0, iData ); - nif->set( idx, "Tangent", tan[i] ); + nif->set( idx, "Tangent", tan[i] ); nif->set( idx, "Bitangent X", bin[i][0] ); auto bitYi = round( ((bin[i][1] + 1.0) / 2.0) * 255.0 ); @@ -275,6 +309,7 @@ QModelIndex spTangentSpace::cast( NifModel * nif, const QModelIndex & iBlock ) nif->set( idx, "Bitangent Y", bitYi ); nif->set( idx, "Bitangent Z", bitZi ); } + nif->restoreState(); } return iShape; From 1c8a9e0d8d06829aad53447c9a8e591fe7a5aa2c Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Fri, 6 Oct 2017 02:28:42 -0400 Subject: [PATCH 076/152] Add editorconfig for GitHub tabsize This is supposed to keep tab size from being 8 in diffs and source views. --- .editorconfig | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..eb1557129 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +root = true + +[*.{h,cpp}] +indent_size = 4 +indent_style = tab + +[*.{pro,pri}] +indent_size = 4 +indent_style = tab + +[*.ui] +indent_size = 1 +indent_style = space + +[*.{frag,prog,vert}] +indent_size = 4 +indent_style = tab From e2b4b7667c4b22fda483c61d84f6c0a75f21fec7 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Wed, 11 Oct 2017 03:09:59 -0400 Subject: [PATCH 077/152] [GL] Switch to SOIL2, unswap ATI2/BC5 channels I was needlessly swapping the channels and all the subsequent math to compensate. Since SSE can technically (though not practically) use ATI2/BC5 I swapped everything back so that the shader math would be correct. Also switched from SOIL to SOIL2. --- NifSkope.pro | 37 +- lib/{soil/SOIL.c => SOIL2/SOIL2.c} | 1545 +++- lib/{soil/SOIL.h => SOIL2/SOIL2.h} | 100 +- lib/SOIL2/etc1_utils.c | 680 ++ lib/SOIL2/etc1_utils.h | 106 + lib/{soil => SOIL2}/image_DXT.c | 0 lib/{soil => SOIL2}/image_DXT.h | 0 lib/{soil => SOIL2}/image_helper.c | 19 +- lib/{soil => SOIL2}/image_helper.h | 0 lib/SOIL2/jo_jpeg.h | 340 + lib/SOIL2/pkm_helper.h | 19 + lib/SOIL2/pvr_helper.h | 264 + lib/SOIL2/stb_image.h | 7240 +++++++++++++++++ lib/SOIL2/stb_image_write.h | 1092 +++ lib/SOIL2/stbi_DDS.h | 34 + .../stbi_DDS_aug_c.h => SOIL2/stbi_DDS_c.h} | 306 +- lib/SOIL2/stbi_ext.h | 28 + lib/SOIL2/stbi_ext_c.h | 74 + lib/SOIL2/stbi_pkm.h | 34 + lib/SOIL2/stbi_pkm_c.h | 227 + lib/SOIL2/stbi_pvr.h | 34 + lib/SOIL2/stbi_pvr_c.h | 1001 +++ lib/soil/stb_image_aug.c | 3698 --------- lib/soil/stb_image_aug.h | 354 - lib/soil/stbi_DDS_aug.h | 21 - res/shaders/fo4_default.frag | 4 +- res/shaders/fo4_default.vert | 7 +- res/shaders/fo4_effectshader.frag | 2 +- res/shaders/fo4_effectshader.vert | 7 +- res/shaders/fo4_env.frag | 6 +- res/shaders/fo4_env.vert | 7 +- src/gl/gltexloaders.cpp | 4 +- 32 files changed, 12786 insertions(+), 4504 deletions(-) rename lib/{soil/SOIL.c => SOIL2/SOIL2.c} (57%) rename lib/{soil/SOIL.h => SOIL2/SOIL2.h} (84%) create mode 100644 lib/SOIL2/etc1_utils.c create mode 100644 lib/SOIL2/etc1_utils.h rename lib/{soil => SOIL2}/image_DXT.c (100%) rename lib/{soil => SOIL2}/image_DXT.h (100%) rename lib/{soil => SOIL2}/image_helper.c (95%) rename lib/{soil => SOIL2}/image_helper.h (100%) create mode 100644 lib/SOIL2/jo_jpeg.h create mode 100644 lib/SOIL2/pkm_helper.h create mode 100644 lib/SOIL2/pvr_helper.h create mode 100644 lib/SOIL2/stb_image.h create mode 100644 lib/SOIL2/stb_image_write.h create mode 100644 lib/SOIL2/stbi_DDS.h rename lib/{soil/stbi_DDS_aug_c.h => SOIL2/stbi_DDS_c.h} (67%) create mode 100644 lib/SOIL2/stbi_ext.h create mode 100644 lib/SOIL2/stbi_ext_c.h create mode 100644 lib/SOIL2/stbi_pkm.h create mode 100644 lib/SOIL2/stbi_pkm_c.h create mode 100644 lib/SOIL2/stbi_pvr.h create mode 100644 lib/SOIL2/stbi_pvr_c.h delete mode 100644 lib/soil/stb_image_aug.c delete mode 100644 lib/soil/stb_image_aug.h delete mode 100644 lib/soil/stbi_DDS_aug.h diff --git a/NifSkope.pro b/NifSkope.pro index 0c45d95a8..72983b4ac 100644 --- a/NifSkope.pro +++ b/NifSkope.pro @@ -17,7 +17,7 @@ contains(QT_VERSION, ^5\\.[0-6]\\..*) { CONFIG += c++14 # Dependencies -CONFIG += nvtristrip qhull soil zlib lz4 fsengine +CONFIG += nvtristrip qhull soil2 zlib lz4 fsengine # Debug/Release options CONFIG(debug, debug|release) { @@ -359,20 +359,31 @@ qhull { lib/qhull/src/libqhull/user.h } -soil { - INCLUDEPATH += lib/soil +soil2 { + INCLUDEPATH += lib/SOIL2 HEADERS += \ - lib/soil/image_DXT.h \ - lib/soil/image_helper.h \ - lib/soil/SOIL.h \ - lib/soil/stb_image_aug.h \ - lib/soil/stbi_DDS_aug.h \ - lib/soil/stbi_DDS_aug_c.h + lib/SOIL2/etc1_utils.h \ + lib/SOIL2/image_DXT.h \ + lib/SOIL2/image_helper.h \ + lib/SOIL2/jo_jpeg.h \ + lib/SOIL2/pkm_helper.h \ + lib/SOIL2/pvr_helper.h \ + lib/SOIL2/SOIL2.h \ + lib/SOIL2/stb_image.h \ + lib/SOIL2/stb_image_write.h \ + lib/SOIL2/stbi_DDS.h \ + lib/SOIL2/stbi_DDS_c.h \ + lib/SOIL2/stbi_ext.h \ + lib/SOIL2/stbi_ext_c.h \ + lib/SOIL2/stbi_pkm.h \ + lib/SOIL2/stbi_pkm_c.h \ + lib/SOIL2/stbi_pvr.h \ + lib/SOIL2/stbi_pvr_c.h SOURCES += \ - lib/soil/image_DXT.c \ - lib/soil/image_helper.c \ - lib/soil/SOIL.c \ - lib/soil/stb_image_aug.c + lib/SOIL2/etc1_utils.c \ + lib/SOIL2/image_DXT.c \ + lib/SOIL2/image_helper.c \ + lib/SOIL2/SOIL2.c } zlib { diff --git a/lib/soil/SOIL.c b/lib/SOIL2/SOIL2.c similarity index 57% rename from lib/soil/SOIL.c rename to lib/SOIL2/SOIL2.c index af28c9565..ee14a1057 100644 --- a/lib/soil/SOIL.c +++ b/lib/SOIL2/SOIL2.c @@ -1,8 +1,11 @@ /* + Fork by Martin Lucas Golini + + Original author Jonathan Dummer 2007-07-26-10.36 - Simple OpenGL Image Library + Simple OpenGL Image Library 2 Public Domain using Sean Barret's stb_image as a base @@ -13,42 +16,110 @@ * everybody at gamedev.net */ -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable: 4018 4100 4244) -#else -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wsign-compare" -#pragma GCC diagnostic ignored "-Wunused-parameter" +#define SOIL_CHECK_FOR_GL_ERRORS 0 + +#if defined( __APPLE_CC__ ) || defined ( __APPLE__ ) + #include + + #if defined( __IPHONE__ ) || ( defined( TARGET_OS_IPHONE ) && TARGET_OS_IPHONE ) || ( defined( TARGET_IPHONE_SIMULATOR ) && TARGET_IPHONE_SIMULATOR ) + #define SOIL_PLATFORM_IOS + #include + #else + #define SOIL_PLATFORM_OSX + #endif +#elif defined( __ANDROID__ ) || defined( ANDROID ) + #define SOIL_PLATFORM_ANDROID +#elif ( defined ( linux ) || defined( __linux__ ) || defined( __FreeBSD__ ) || defined(__OpenBSD__) || defined( __NetBSD__ ) || defined( __DragonFly__ ) || defined( __SVR4 ) ) + #define SOIL_X11_PLATFORM #endif -#define SOIL_CHECK_FOR_GL_ERRORS 0 +#if ( defined( SOIL_PLATFORM_IOS ) || defined( SOIL_PLATFORM_ANDROID ) ) && ( !defined( SOIL_GLES1 ) && !defined( SOIL_GLES2 ) ) + #define SOIL_GLES2 +#endif + +#if ( defined( SOIL_GLES2 ) || defined( SOIL_GLES1 ) ) && !defined( SOIL_NO_EGL ) && !defined( SOIL_PLATFORM_IOS ) + #include +#endif -#ifdef WIN32 +#if defined( SOIL_GLES2 ) + #ifdef SOIL_PLATFORM_IOS + #include + #include + #else + #include + #include + #endif + + #define APIENTRY GL_APIENTRY +#elif defined( SOIL_GLES1 ) + #ifndef GL_GLEXT_PROTOTYPES + #define GL_GLEXT_PROTOTYPES + #endif + #ifdef SOIL_PLATFORM_IOS + #include + #include + #else + #include + #include + #endif + + #define APIENTRY GL_APIENTRY +#else + +#if defined( __WIN32__ ) || defined( _WIN32 ) || defined( WIN32 ) + #define SOIL_PLATFORM_WIN32 #define WIN32_LEAN_AND_MEAN #include #include #include + + #ifndef GL_UNSIGNED_SHORT_4_4_4_4 + #define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 + #endif + #ifndef GL_UNSIGNED_SHORT_5_5_5_1 + #define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 + #endif + #ifndef GL_UNSIGNED_SHORT_5_6_5 + #define GL_UNSIGNED_SHORT_5_6_5 0x8363 + #endif #elif defined(__APPLE__) || defined(__APPLE_CC__) /* I can't test this Apple stuff! */ #include #include #define APIENTRY -#else +#elif defined( SOIL_X11_PLATFORM ) #include #include +#else + #include +#endif + +#endif + +#ifndef GL_BGRA +#define GL_BGRA 0x80E1 +#endif + +#ifndef GL_RG +#define GL_RG 0x8227 #endif -#include "SOIL.h" -#include "stb_image_aug.h" +#include "SOIL2.h" +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "stb_image_write.h" #include "image_helper.h" #include "image_DXT.h" +#include "pvr_helper.h" +#include "pkm_helper.h" +#include "jo_jpeg.h" #include #include /* error reporting */ -char *result_string_pointer = "SOIL initialized"; +const char *result_string_pointer = "SOIL initialized"; /* for loading cube maps */ enum{ @@ -73,6 +144,7 @@ int query_cubemap_capability( void ); #define SOIL_PROXY_TEXTURE_CUBE_MAP 0x851B #define SOIL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C /* for non-power-of-two texture */ +#define SOIL_IS_POW2( v ) ( ( v & ( v - 1 ) ) == 0 ) static int has_NPOT_capability = SOIL_CAPABILITY_UNKNOWN; int query_NPOT_capability( void ); /* for texture rectangles */ @@ -83,29 +155,208 @@ int query_tex_rectangle_capability( void ); /* for using DXT compression */ static int has_DXT_capability = SOIL_CAPABILITY_UNKNOWN; int query_DXT_capability( void ); +#define SOIL_GL_SRGB 0x8C40 +#define SOIL_GL_SRGB_ALPHA 0x8C42 #define SOIL_RGB_S3TC_DXT1 0x83F0 #define SOIL_RGBA_S3TC_DXT1 0x83F1 #define SOIL_RGBA_S3TC_DXT3 0x83F2 #define SOIL_RGBA_S3TC_DXT5 0x83F3 +#define SOIL_GL_COMPRESSED_SRGB_S3TC_DXT1_EXT 0x8C4C +#define SOIL_GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT 0x8C4F +static int has_sRGB_capability = SOIL_CAPABILITY_UNKNOWN; +int query_sRGB_capability( void ); typedef void (APIENTRY * P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid * data); -P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC soilGlCompressedTexImage2D = NULL; -unsigned int SOIL_direct_load_DDS( - const char *filename, - unsigned int reuse_texture_ID, - int flags, - int loading_as_cubemap ); -unsigned int SOIL_direct_load_DDS_from_memory( - const unsigned char *const buffer, - int buffer_length, - unsigned int reuse_texture_ID, - int flags, - int loading_as_cubemap ); +static P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC soilGlCompressedTexImage2D = NULL; + +typedef void (APIENTRY *P_SOIL_GLGENERATEMIPMAPPROC)(GLenum target); +static P_SOIL_GLGENERATEMIPMAPPROC soilGlGenerateMipmap = NULL; + +static int has_gen_mipmap_capability = SOIL_CAPABILITY_UNKNOWN; +static int query_gen_mipmap_capability( void ); + +static int has_PVR_capability = SOIL_CAPABILITY_UNKNOWN; +int query_PVR_capability( void ); +static int has_BGRA8888_capability = SOIL_CAPABILITY_UNKNOWN; +int query_BGRA8888_capability( void ); +static int has_ETC1_capability = SOIL_CAPABILITY_UNKNOWN; +int query_ETC1_capability( void ); + +/* GL_IMG_texture_compression_pvrtc */ +#define SOIL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00 +#define SOIL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG 0x8C01 +#define SOIL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02 +#define SOIL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG 0x8C03 +#define SOIL_GL_ETC1_RGB8_OES 0x8D64 + +#if defined( SOIL_X11_PLATFORM ) || defined( SOIL_PLATFORM_WIN32 ) || defined( SOIL_PLATFORM_OSX ) +typedef const GLubyte *(APIENTRY * P_SOIL_glGetStringiFunc) (GLenum, GLuint); +static P_SOIL_glGetStringiFunc soilGlGetStringiFunc = NULL; + +static int isAtLeastGL3() +{ + static int is_gl3 = SOIL_CAPABILITY_UNKNOWN; + + if ( SOIL_CAPABILITY_UNKNOWN == is_gl3 ) + { + const char * verstr = (const char *) glGetString( GL_VERSION ); + is_gl3 = ( verstr && ( atoi(verstr) >= 3 ) ); + } + + return is_gl3; +} +#endif + +#ifdef SOIL_PLATFORM_WIN32 +static int soilTestWinProcPointer(const PROC pTest) +{ + ptrdiff_t iTest; + if(!pTest) return 0; + iTest = (ptrdiff_t)pTest; + if(iTest == 1 || iTest == 2 || iTest == 3 || iTest == -1) return 0; + return 1; +} +#endif + +void * SOIL_GL_GetProcAddress(const char *proc) +{ + void *func = NULL; + +#if defined( SOIL_PLATFORM_IOS ) + func = dlsym( RTLD_DEFAULT, proc ); +#elif defined( SOIL_GLES2 ) || defined( SOIL_GLES1 ) + #ifndef SOIL_NO_EGL + func = eglGetProcAddress( proc ); + #else + func = NULL; + #endif +#elif defined( SOIL_PLATFORM_WIN32 ) + func = wglGetProcAddress( proc ); + + if (!soilTestWinProcPointer((const PROC)func)) + func = NULL; +#elif defined( SOIL_PLATFORM_OSX ) + /* I can't test this Apple stuff! */ + CFBundleRef bundle; + CFURLRef bundleURL = + CFURLCreateWithFileSystemPath( + kCFAllocatorDefault, + CFSTR("/System/Library/Frameworks/OpenGL.framework"), + kCFURLPOSIXPathStyle, + true ); + CFStringRef extensionName = + CFStringCreateWithCString( + kCFAllocatorDefault, + proc, + kCFStringEncodingASCII ); + bundle = CFBundleCreate( kCFAllocatorDefault, bundleURL ); + assert( bundle != NULL ); + + func = CFBundleGetFunctionPointerForName( bundle, extensionName ); + + CFRelease( bundleURL ); + CFRelease( extensionName ); + CFRelease( bundle ); +#elif defined( SOIL_X11_PLATFORM ) + func = +#if !defined(GLX_VERSION_1_4) + glXGetProcAddressARB +#else + glXGetProcAddress +#endif + ( (const GLubyte *)proc ); +#endif + + return func; +} + +/* Based on the SDL2 implementation */ +int SOIL_GL_ExtensionSupported(const char *extension) +{ + const char *extensions; + const char *start; + const char *where, *terminator; + + /* Extension names should not have spaces. */ + where = strchr(extension, ' '); + + if (where || *extension == '\0') + { + return 0; + } + + #if defined( SOIL_X11_PLATFORM ) || defined( SOIL_PLATFORM_WIN32 ) || defined( SOIL_PLATFORM_OSX ) + /* Lookup the available extensions */ + if ( isAtLeastGL3() ) + { + GLint num_exts = 0; + GLint i; + + if ( NULL == soilGlGetStringiFunc ) + { + soilGlGetStringiFunc = (P_SOIL_glGetStringiFunc)SOIL_GL_GetProcAddress("glGetStringi"); + + if ( NULL == soilGlGetStringiFunc ) + { + return 0; + } + } + + #ifndef GL_NUM_EXTENSIONS + #define GL_NUM_EXTENSIONS 0x821D + #endif + glGetIntegerv(GL_NUM_EXTENSIONS, &num_exts); + for (i = 0; i < num_exts; i++) + { + const char *thisext = (const char *) soilGlGetStringiFunc(GL_EXTENSIONS, i); + + if (strcmp(thisext, extension) == 0) + { + return 1; + } + } + + return 0; + } + #endif + + /* Try the old way with glGetString(GL_EXTENSIONS) ... */ + extensions = (const char *) glGetString(GL_EXTENSIONS); + + if (!extensions) + { + return 0; + } + + /* + * It takes a bit of care to be fool-proof about parsing the OpenGL + * extensions string. Don't be fooled by sub-strings, etc. + */ + start = extensions; + + for (;;) { + where = strstr(start, extension); + + if (!where) + break; + + terminator = where + strlen(extension); + + if (where == start || *(where - 1) == ' ') + if (*terminator == ' ' || *terminator == '\0') + return 1; + + start = terminator; + } + + return 0; +} + /* other functions */ unsigned int SOIL_internal_create_OGL_texture ( const unsigned char *const data, - int width, int height, int channels, + int *width, int *height, int channels, unsigned int reuse_texture_ID, unsigned int flags, unsigned int opengl_texture_type, @@ -141,6 +392,27 @@ unsigned int return tex_id; } } + + if( flags & SOIL_FLAG_PVR_LOAD_DIRECT ) + { + tex_id = SOIL_direct_load_PVR( filename, reuse_texture_ID, flags, 0 ); + if( tex_id ) + { + /* hey, it worked!! */ + return tex_id; + } + } + + if( flags & SOIL_FLAG_ETC1_LOAD_DIRECT ) + { + tex_id = SOIL_direct_load_ETC1( filename, reuse_texture_ID, flags ); + if( tex_id ) + { + /* hey, it worked!! */ + return tex_id; + } + } + /* try to load the image */ img = SOIL_load_image( filename, &width, &height, &channels, force_channels ); /* channels holds the original number of channels, which may have been forced */ @@ -156,7 +428,7 @@ unsigned int } /* OK, make it a texture! */ tex_id = SOIL_internal_create_OGL_texture( - img, width, height, channels, + img, &width, &height, channels, reuse_texture_ID, flags, GL_TEXTURE_2D, GL_TEXTURE_2D, GL_MAX_TEXTURE_SIZE ); @@ -177,7 +449,7 @@ unsigned int ) { /* variables */ - unsigned char* img; + unsigned char* img = NULL; int width, height, channels; unsigned int tex_id; /* no direct uploading of the image as a DDS file */ @@ -189,8 +461,14 @@ unsigned int result_string_pointer = "Invalid fake HDR format specified"; return 0; } - /* try to load the image (only the HDR type) */ - img = stbi_hdr_load_rgbe( filename, &width, &height, &channels, 4 ); + + /* check if the image is HDR */ + if ( stbi_is_hdr( filename ) ) + { + /* try to load the image (only the HDR type) */ + img = stbi_load( filename, &width, &height, &channels, 4 ); + } + /* channels holds the original number of channels, which may have been forced */ if( NULL == img ) { @@ -208,7 +486,7 @@ unsigned int } /* OK, make it a texture! */ tex_id = SOIL_internal_create_OGL_texture( - img, width, height, channels, + img, &width, &height, channels, reuse_texture_ID, flags, GL_TEXTURE_2D, GL_TEXTURE_2D, GL_MAX_TEXTURE_SIZE ); @@ -248,6 +526,31 @@ unsigned int return tex_id; } } + + if( flags & SOIL_FLAG_PVR_LOAD_DIRECT ) + { + tex_id = SOIL_direct_load_PVR_from_memory( + buffer, buffer_length, + reuse_texture_ID, flags, 0 ); + if( tex_id ) + { + /* hey, it worked!! */ + return tex_id; + } + } + + if( flags & SOIL_FLAG_ETC1_LOAD_DIRECT ) + { + tex_id = SOIL_direct_load_ETC1_from_memory( + buffer, buffer_length, + reuse_texture_ID, flags ); + if( tex_id ) + { + /* hey, it worked!! */ + return tex_id; + } + } + /* try to load the image */ img = SOIL_load_image_from_memory( buffer, buffer_length, @@ -266,7 +569,7 @@ unsigned int } /* OK, make it a texture! */ tex_id = SOIL_internal_create_OGL_texture( - img, width, height, channels, + img, &width, &height, channels, reuse_texture_ID, flags, GL_TEXTURE_2D, GL_TEXTURE_2D, GL_MAX_TEXTURE_SIZE ); @@ -326,7 +629,7 @@ unsigned int } /* upload the texture, and create a texture ID if necessary */ tex_id = SOIL_internal_create_OGL_texture( - img, width, height, channels, + img, &width, &height, channels, reuse_texture_ID, flags, SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_POSITIVE_X, SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); @@ -350,7 +653,7 @@ unsigned int } /* upload the texture, but reuse the assigned texture ID */ tex_id = SOIL_internal_create_OGL_texture( - img, width, height, channels, + img, &width, &height, channels, tex_id, flags, SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_NEGATIVE_X, SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); @@ -375,7 +678,7 @@ unsigned int } /* upload the texture, but reuse the assigned texture ID */ tex_id = SOIL_internal_create_OGL_texture( - img, width, height, channels, + img, &width, &height, channels, tex_id, flags, SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_POSITIVE_Y, SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); @@ -400,7 +703,7 @@ unsigned int } /* upload the texture, but reuse the assigned texture ID */ tex_id = SOIL_internal_create_OGL_texture( - img, width, height, channels, + img, &width, &height, channels, tex_id, flags, SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Y, SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); @@ -425,7 +728,7 @@ unsigned int } /* upload the texture, but reuse the assigned texture ID */ tex_id = SOIL_internal_create_OGL_texture( - img, width, height, channels, + img, &width, &height, channels, tex_id, flags, SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_POSITIVE_Z, SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); @@ -450,7 +753,7 @@ unsigned int } /* upload the texture, but reuse the assigned texture ID */ tex_id = SOIL_internal_create_OGL_texture( - img, width, height, channels, + img, &width, &height, channels, tex_id, flags, SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Z, SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); @@ -519,7 +822,7 @@ unsigned int } /* upload the texture, and create a texture ID if necessary */ tex_id = SOIL_internal_create_OGL_texture( - img, width, height, channels, + img, &width, &height, channels, reuse_texture_ID, flags, SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_POSITIVE_X, SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); @@ -545,7 +848,7 @@ unsigned int } /* upload the texture, but reuse the assigned texture ID */ tex_id = SOIL_internal_create_OGL_texture( - img, width, height, channels, + img, &width, &height, channels, tex_id, flags, SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_NEGATIVE_X, SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); @@ -572,7 +875,7 @@ unsigned int } /* upload the texture, but reuse the assigned texture ID */ tex_id = SOIL_internal_create_OGL_texture( - img, width, height, channels, + img, &width, &height, channels, tex_id, flags, SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_POSITIVE_Y, SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); @@ -599,7 +902,7 @@ unsigned int } /* upload the texture, but reuse the assigned texture ID */ tex_id = SOIL_internal_create_OGL_texture( - img, width, height, channels, + img, &width, &height, channels, tex_id, flags, SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Y, SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); @@ -626,7 +929,7 @@ unsigned int } /* upload the texture, but reuse the assigned texture ID */ tex_id = SOIL_internal_create_OGL_texture( - img, width, height, channels, + img, &width, &height, channels, tex_id, flags, SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_POSITIVE_Z, SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); @@ -653,7 +956,7 @@ unsigned int } /* upload the texture, but reuse the assigned texture ID */ tex_id = SOIL_internal_create_OGL_texture( - img, width, height, channels, + img, &width, &height, channels, tex_id, flags, SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Z, SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); @@ -698,6 +1001,22 @@ unsigned int return tex_id; } } + + if ( flags & SOIL_FLAG_PVR_LOAD_DIRECT ) + { + tex_id = SOIL_direct_load_PVR( filename, reuse_texture_ID, flags, 1 ); + if( tex_id ) + { + /* hey, it worked!! */ + return tex_id; + } + } + + if ( flags & SOIL_FLAG_ETC1_LOAD_DIRECT ) + { + return 0; + } + /* face order checking */ for( i = 0; i < 6; ++i ) { @@ -786,6 +1105,24 @@ unsigned int return tex_id; } } + + if ( flags & SOIL_FLAG_PVR_LOAD_DIRECT ) + { + tex_id = SOIL_direct_load_PVR_from_memory( + buffer, buffer_length, + reuse_texture_ID, flags, 1 ); + if ( tex_id ) + { + /* hey, it worked!! */ + return tex_id; + } + } + + if ( flags & SOIL_FLAG_ETC1_LOAD_DIRECT ) + { + return 0; + } + /* face order checking */ for( i = 0; i < 6; ++i ) { @@ -939,7 +1276,7 @@ unsigned int } /* upload it as a texture */ tex_id = SOIL_internal_create_OGL_texture( - sub_img, sz, sz, channels, + sub_img, &sz, &sz, channels, tex_id, flags, SOIL_TEXTURE_CUBE_MAP, cubemap_target, @@ -955,7 +1292,7 @@ unsigned int SOIL_create_OGL_texture ( const unsigned char *const data, - int width, int height, int channels, + int *width, int *height, int channels, unsigned int reuse_texture_ID, unsigned int flags ) @@ -986,11 +1323,91 @@ void check_for_GL_errors( const char *calling_location ) } #endif +static void createMipmaps(const unsigned char *const img, + int width, int height, int channels, + unsigned int flags, + unsigned int opengl_texture_target, + unsigned int internal_texture_format, + unsigned int original_texture_format, + int DXT_mode) +{ + if ( ( flags & SOIL_FLAG_GL_MIPMAPS ) && query_gen_mipmap_capability() == SOIL_CAPABILITY_PRESENT ) + { + soilGlGenerateMipmap(opengl_texture_target); + } + else + { + int MIPlevel = 1; + int MIPwidth = (width+1) / 2; + int MIPheight = (height+1) / 2; + unsigned char *resampled = (unsigned char*)malloc( channels*MIPwidth*MIPheight ); + + while( ((1< 0; --i ) + int index1 = j * iwidth * channels; + int index2 = (iheight - 1 - j) * iwidth * channels; + for( i = iwidth * channels; i > 0; --i ) { unsigned char temp = img[index1]; img[index1] = img[index2]; @@ -1058,7 +1503,7 @@ unsigned int /* does the user want me to scale the colors into the NTSC safe RGB range? */ if( flags & SOIL_FLAG_NTSC_SAFE_RGB ) { - scale_image_RGB_to_NTSC_safe( img, width, height, channels ); + scale_image_RGB_to_NTSC_safe( img, iwidth, iheight, channels ); } /* does the user want me to convert from straight to pre-multiplied alpha? (and do we even _have_ alpha?) */ @@ -1068,13 +1513,13 @@ unsigned int switch( channels ) { case 2: - for( i = 0; i < 2*width*height; i += 2 ) + for( i = 0; i < 2*iwidth*iheight; i += 2 ) { img[i] = (img[i] * img[i+1] + 128) >> 8; } break; case 4: - for( i = 0; i < 4*width*height; i += 4 ) + for( i = 0; i < 4*iwidth*iheight; i += 4 ) { img[i+0] = (img[i+0] * img[i+3] + 128) >> 8; img[i+1] = (img[i+1] * img[i+3] + 128) >> 8; @@ -1086,98 +1531,88 @@ unsigned int break; } } - /* if the user can't support NPOT textures, make sure we force the POT option */ - if( (query_NPOT_capability() == SOIL_CAPABILITY_NONE) && - !(flags & SOIL_FLAG_TEXTURE_RECTANGLE) ) - { - /* add in the POT flag */ - flags |= SOIL_FLAG_POWER_OF_TWO; - } - /* how large of a texture can this OpenGL implementation handle? */ - /* texture_check_size_enum will be GL_MAX_TEXTURE_SIZE or SOIL_MAX_CUBE_MAP_TEXTURE_SIZE */ - glGetIntegerv( texture_check_size_enum, &max_supported_size ); + /* do I need to make it a power of 2? */ if( - (flags & SOIL_FLAG_POWER_OF_TWO) || /* user asked for it */ - (flags & SOIL_FLAG_MIPMAPS) || /* need it for the MIP-maps */ - (width > max_supported_size) || /* it's too big, (make sure it's */ - (height > max_supported_size) ) /* 2^n for later down-sampling) */ + ( ( flags & SOIL_FLAG_POWER_OF_TWO) && ( !SOIL_IS_POW2(iwidth) || !SOIL_IS_POW2(iheight) ) ) || /* user asked for it and the texture is not power of 2 */ + ( (flags & SOIL_FLAG_MIPMAPS)&& !( ( flags & SOIL_FLAG_GL_MIPMAPS ) && + query_gen_mipmap_capability() == SOIL_CAPABILITY_PRESENT && + query_NPOT_capability() == SOIL_CAPABILITY_PRESENT ) ) || /* need it for the MIP-maps when mipmaps required + and not GL mipmaps required and supported */ + (iwidth > max_supported_size) || /* it's too big, (make sure it's */ + (iheight > max_supported_size) ) /* 2^n for later down-sampling) */ { int new_width = 1; int new_height = 1; - while( new_width < width ) + while( new_width < iwidth ) { new_width *= 2; } - while( new_height < height ) + while( new_height < iheight ) { new_height *= 2; } /* still? */ - if( (new_width != width) || (new_height != height) ) + if( (new_width != iwidth) || (new_height != iheight) ) { /* yep, resize */ unsigned char *resampled = (unsigned char*)malloc( channels*new_width*new_height ); up_scale_image( - img, width, height, channels, + NULL != img ? img : data, iwidth, iheight, channels, resampled, new_width, new_height ); - /* OJO this is for debug only! */ - /* - SOIL_save_image( "\\showme.bmp", SOIL_SAVE_TYPE_BMP, - new_width, new_height, channels, - resampled ); - */ - /* nuke the old guy, then point it at the new guy */ + + /* nuke the old guy ( if a copy exists ), then point it at the new guy */ SOIL_free_image_data( img ); img = resampled; - width = new_width; - height = new_height; + *width = new_width; + *height = new_height; + iwidth = new_width; + iheight = new_height; } } /* now, if it is too large... */ - if( (width > max_supported_size) || (height > max_supported_size) ) + if( (iwidth > max_supported_size) || (iheight > max_supported_size) ) { /* I've already made it a power of two, so simply use the MIPmapping code to reduce its size to the allowable maximum. */ unsigned char *resampled; int reduce_block_x = 1, reduce_block_y = 1; int new_width, new_height; - if( width > max_supported_size ) + if( iwidth > max_supported_size ) { - reduce_block_x = width / max_supported_size; + reduce_block_x = iwidth / max_supported_size; } - if( height > max_supported_size ) + if( iheight > max_supported_size ) { - reduce_block_y = height / max_supported_size; + reduce_block_y = iheight / max_supported_size; } - new_width = width / reduce_block_x; - new_height = height / reduce_block_y; + new_width = iwidth / reduce_block_x; + new_height = iheight / reduce_block_y; resampled = (unsigned char*)malloc( channels*new_width*new_height ); /* perform the actual reduction */ - mipmap_image( img, width, height, channels, + mipmap_image( NULL != img ? img : data, iwidth, iheight, channels, resampled, reduce_block_x, reduce_block_y ); /* nuke the old guy, then point it at the new guy */ SOIL_free_image_data( img ); img = resampled; - width = new_width; - height = new_height; + *width = new_width; + *height = new_height; + iwidth = new_width; + iheight = new_height; } /* does the user want us to use YCoCg color space? */ if( flags & SOIL_FLAG_CoCg_Y ) { /* this will only work with RGB and RGBA images */ - convert_RGB_to_YCoCg( img, width, height, channels ); - /* - save_image_as_DDS( "CoCg_Y.dds", width, height, channels, img ); - */ + convert_RGB_to_YCoCg( img, iwidth, iheight, channels ); } /* create the OpenGL texture ID handle - (note: allowing a forced texture ID lets me reload a texture) */ - tex_id = reuse_texture_ID; - if( tex_id == 0 ) - { + (note: allowing a forced texture ID lets me reload a texture) */ + tex_id = reuse_texture_ID; + if( tex_id == 0 ) + { glGenTextures( 1, &tex_id ); - } + } check_for_GL_errors( "glGenTextures" ); /* Note: sometimes glGenTextures fails (usually no OpenGL context) */ if( tex_id ) @@ -1209,18 +1644,39 @@ unsigned int if( (channels & 1) == 1 ) { /* 1 or 3 channels = DXT1 */ - internal_texture_format = SOIL_RGB_S3TC_DXT1; + internal_texture_format = sRGB_texture ? SOIL_GL_COMPRESSED_SRGB_S3TC_DXT1_EXT : SOIL_RGB_S3TC_DXT1; } else { /* 2 or 4 channels = DXT5 */ - internal_texture_format = SOIL_RGBA_S3TC_DXT5; + internal_texture_format = sRGB_texture ? SOIL_GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT : SOIL_RGBA_S3TC_DXT5; } } } + else if ( sRGB_texture ) + { + switch( channels ) + { + case 3: + internal_texture_format = SOIL_GL_SRGB; + break; + case 4: + internal_texture_format = SOIL_GL_SRGB_ALPHA; + break; + } + } + /* bind an OpenGL texture ID */ glBindTexture( opengl_texture_type, tex_id ); check_for_GL_errors( "glBindTexture" ); - /* upload the main image */ + + /* set the unpack aligment */ + glGetIntegerv(GL_UNPACK_ALIGNMENT, &unpack_aligment); + if ( 1 != unpack_aligment ) + { + glPixelStorei(GL_UNPACK_ALIGNMENT,1); + } + + /* upload the main image */ if( DXT_mode == SOIL_CAPABILITY_PRESENT ) { /* user wants me to do the DXT conversion! */ @@ -1229,17 +1685,17 @@ unsigned int if( (channels & 1) == 1 ) { /* RGB, use DXT1 */ - DDS_data = convert_image_to_DXT1( img, width, height, channels, &DDS_size ); + DDS_data = convert_image_to_DXT1( NULL != img ? img : data, iwidth, iheight, channels, &DDS_size ); } else { /* RGBA, use DXT5 */ - DDS_data = convert_image_to_DXT5( img, width, height, channels, &DDS_size ); + DDS_data = convert_image_to_DXT5( NULL != img ? img : data, iwidth, iheight, channels, &DDS_size ); } if( DDS_data ) { soilGlCompressedTexImage2D( opengl_texture_target, 0, - internal_texture_format, width, height, 0, + internal_texture_format, iwidth, iheight, 0, DDS_size, DDS_data ); check_for_GL_errors( "glCompressedTexImage2D" ); SOIL_free_image_data( DDS_data ); @@ -1249,8 +1705,8 @@ unsigned int /* my compression failed, try the OpenGL driver's version */ glTexImage2D( opengl_texture_target, 0, - internal_texture_format, width, height, 0, - original_texture_format, GL_UNSIGNED_BYTE, img ); + internal_texture_format, iwidth, iheight, 0, + original_texture_format, GL_UNSIGNED_BYTE, NULL != img ? img : data ); check_for_GL_errors( "glTexImage2D" ); /* printf( "OpenGL DXT compressor\n" ); */ } @@ -1259,74 +1715,18 @@ unsigned int /* user want OpenGL to do all the work! */ glTexImage2D( opengl_texture_target, 0, - internal_texture_format, width, height, 0, - original_texture_format, GL_UNSIGNED_BYTE, img ); + internal_texture_format, iwidth, iheight, 0, + original_texture_format, GL_UNSIGNED_BYTE, NULL != img ? img : data ); + check_for_GL_errors( "glTexImage2D" ); /*printf( "OpenGL DXT compressor\n" ); */ } + /* are any MIPmaps desired? */ - if( flags & SOIL_FLAG_MIPMAPS ) + if( flags & SOIL_FLAG_MIPMAPS || flags & SOIL_FLAG_GL_MIPMAPS ) { - int MIPlevel = 1; - int MIPwidth = (width+1) / 2; - int MIPheight = (height+1) / 2; - unsigned char *resampled = (unsigned char*)malloc( channels*MIPwidth*MIPheight ); - while( ((1< 1) ) { - int shift_offset; mipmaps = header.dwMipMapCount - 1; DDS_full_size = DDS_main_size; - if( uncompressed ) - { - /* uncompressed DDS, simple MIPmap size calculation */ - shift_offset = 0; - } else - { - /* compressed DDS, MIPmap size calculation is block based */ - shift_offset = 2; - } for( i = 1; i <= mipmaps; ++ i ) { int w, h; - w = width >> (shift_offset + i); - h = height >> (shift_offset + i); + w = width >> i; + h = height >> i; if( w < 1 ) { w = 1; @@ -1706,7 +2131,15 @@ unsigned int SOIL_direct_load_DDS_from_memory( { h = 1; } - DDS_full_size += w*h*block_size; + if ( uncompressed ) + { + /* uncompressed DDS, simple MIPmap size calculation */ + DDS_full_size += w*h*block_size; + } else + { + /* compressed DDS, MIPmap size calculation is block based */ + DDS_full_size += ((w+3)/4)*((h+3)/4)*block_size; + } } } else { @@ -1725,7 +2158,7 @@ unsigned int SOIL_direct_load_DDS_from_memory( /* do this for each face of the cubemap! */ for( cf_target = ogl_target_start; cf_target <= ogl_target_end; ++cf_target ) { - if( buffer_index + DDS_full_size <= buffer_length ) + if( buffer_index + DDS_full_size <= (unsigned int)buffer_length ) { unsigned int byte_offset = DDS_main_size; memcpy( (void*)DDS_data, (const void*)(&buffer[buffer_index]), DDS_full_size ); @@ -1735,7 +2168,7 @@ unsigned int SOIL_direct_load_DDS_from_memory( { /* and remember, DXT uncompressed uses BGR(A), so swap to RGB(A) for ALL MIPmap levels */ - for( i = 0; i < DDS_full_size; i += block_size ) + for( i = 0; i < (int)DDS_full_size; i += block_size ) { unsigned char temp = DDS_data[i]; DDS_data[i] = DDS_data[i+2]; @@ -1818,8 +2251,8 @@ unsigned int SOIL_direct_load_DDS_from_memory( glTexParameteri( opengl_texture_type, SOIL_TEXTURE_WRAP_R, GL_REPEAT ); } else { - /* unsigned int clamp_mode = SOIL_CLAMP_TO_EDGE; */ - unsigned int clamp_mode = GL_CLAMP; + unsigned int clamp_mode = SOIL_CLAMP_TO_EDGE; + /* unsigned int clamp_mode = GL_CLAMP; */ glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_S, clamp_mode ); glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_T, clamp_mode ); glTexParameteri( opengl_texture_type, SOIL_TEXTURE_WRAP_R, clamp_mode ); @@ -1873,12 +2306,466 @@ unsigned int SOIL_direct_load_DDS( } /* now try to do the loading */ tex_ID = SOIL_direct_load_DDS_from_memory( - (const unsigned char *const)buffer, buffer_length, + (const unsigned char *const)buffer, (int)buffer_length, + reuse_texture_ID, flags, loading_as_cubemap ); + SOIL_free_image_data( buffer ); + return tex_ID; +} + +unsigned int SOIL_direct_load_PVR_from_memory( + const unsigned char *const buffer, + int buffer_length, + unsigned int reuse_texture_ID, + int flags, + int loading_as_cubemap ) +{ + PVR_Texture_Header* header = (PVR_Texture_Header*)buffer; + int num_surfs = 1; + GLuint tex_ID = 0; + GLenum PVR_format = 0; + GLenum PVR_type = GL_RGB; + unsigned int opengl_texture_type = loading_as_cubemap ? SOIL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D; + int is_PVRTC_supported = query_PVR_capability() == SOIL_CAPABILITY_PRESENT; + int is_BGRA8888_supported = query_BGRA8888_capability() == SOIL_CAPABILITY_PRESENT; + int is_compressed_format_supported = 0; + int is_compressed_format = 0; + int mipmaps = 0; + int i; + GLint unpack_aligment; + + // Check the header size + if ( header->dwHeaderSize != sizeof(PVR_Texture_Header) ) { + if ( header->dwHeaderSize == PVRTEX_V1_HEADER_SIZE ) { + result_string_pointer = "this is an old pvr ( update the PVR file )"; + + if ( loading_as_cubemap ) { + if( header->dwpfFlags & PVRTEX_CUBEMAP ) { + num_surfs = 6; + } else { + result_string_pointer = "tried to load a non-cubemap PVR as cubemap"; + return 0; + } + } + } else { + result_string_pointer = "invalid PVR header"; + + return 0; + } + } else { + if ( loading_as_cubemap ) { + // Header V2 + if( header->dwNumSurfs < 1 ) { + if( header->dwpfFlags & PVRTEX_CUBEMAP ) { + num_surfs = 6; + } else { + result_string_pointer = "tried to load a non-cubemap PVR as cubemap"; + return 0; + } + } else { + num_surfs = header->dwNumSurfs; + } + } + } + + // Check the magic identifier + if ( header->dwPVR != PVRTEX_IDENTIFIER ) { + result_string_pointer = "invalid PVR header"; + return 0; + } + + /* Only accept untwiddled data UNLESS texture format is PVRTC */ + if ( ((header->dwpfFlags & PVRTEX_TWIDDLE) == PVRTEX_TWIDDLE) + && ((header->dwpfFlags & PVRTEX_PIXELTYPE)!=OGL_PVRTC2) + && ((header->dwpfFlags & PVRTEX_PIXELTYPE)!=OGL_PVRTC4) ) + { + // We need to load untwiddled textures -- hw will twiddle for us. + result_string_pointer = "pvr is not compressed ( untwiddled texture )"; + return 0; + } + + switch( header->dwpfFlags & PVRTEX_PIXELTYPE ) + { + case OGL_RGBA_4444: + PVR_format = GL_UNSIGNED_SHORT_4_4_4_4; + PVR_type = GL_RGBA; + break; + case OGL_RGBA_5551: + PVR_format = GL_UNSIGNED_SHORT_5_5_5_1; + PVR_type = GL_RGBA; + break; + case OGL_RGBA_8888: + PVR_format = GL_UNSIGNED_BYTE; + PVR_type = GL_RGBA; + break; + case OGL_RGB_565: + PVR_format = GL_UNSIGNED_SHORT_5_6_5; + PVR_type = GL_RGB; + break; + case OGL_RGB_555: + result_string_pointer = "failed: pixel type OGL_RGB_555 not supported."; + return 0; + case OGL_RGB_888: + PVR_format = GL_UNSIGNED_BYTE; + PVR_type = GL_RGB; + break; + case OGL_I_8: + PVR_format = GL_UNSIGNED_BYTE; + PVR_type = GL_LUMINANCE; + break; + case OGL_AI_88: + PVR_format = GL_UNSIGNED_BYTE; + PVR_type = GL_LUMINANCE_ALPHA; + break; + case MGLPT_PVRTC2: + case OGL_PVRTC2: + if(is_PVRTC_supported) { + is_compressed_format_supported = is_compressed_format = 1; + PVR_format = header->dwAlphaBitMask==0 ? SOIL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG : SOIL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG ; // PVRTC2 + } else { + result_string_pointer = "error: PVRTC2 not supported.Decompress the texture first."; + return 0; + } + break; + case MGLPT_PVRTC4: + case OGL_PVRTC4: + if(is_PVRTC_supported) { + is_compressed_format_supported = is_compressed_format = 1; + PVR_format = header->dwAlphaBitMask==0 ? SOIL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG : SOIL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG ; // PVRTC4 + } else { + result_string_pointer = "error: PVRTC4 not supported. Decompress the texture first."; + return 0; + } + break; + case OGL_BGRA_8888: + if(is_BGRA8888_supported) { + PVR_format = GL_UNSIGNED_BYTE; + PVR_type = GL_BGRA; + break; + } else { + result_string_pointer = "Unable to load GL_BGRA texture as extension GL_IMG_texture_format_BGRA8888 is unsupported."; + return 0; + } + default: // NOT SUPPORTED + result_string_pointer = "failed: pixel type not supported."; + return 0; + } + + #ifdef SOIL_GLES1 + // check that this data is cube map data or not. + if( loading_as_cubemap ) { + result_string_pointer = "cube map textures are not available in GLES1.x."; + return 0; + } + #endif + + // load the texture up + tex_ID = reuse_texture_ID; + if( tex_ID == 0 ) + { + glGenTextures( 1, &tex_ID ); + } + + glBindTexture( opengl_texture_type, tex_ID ); + + if( glGetError() ) { + result_string_pointer = "failed: glBindTexture() failed."; + return 0; + } + + glGetIntegerv(GL_UNPACK_ALIGNMENT, &unpack_aligment); + if ( 1 != unpack_aligment ) + { + glPixelStorei(GL_UNPACK_ALIGNMENT,1); // Never have row-aligned in headers + } + + #define _MAX( a, b ) (( a <= b )? b : a) + for(i=0; idwHeaderSize + header->dwTextureDataSize * i; + char *cur_texture_ptr = 0; + int mipmap_level; + unsigned int width= header->dwWidth, height = header->dwHeight; + unsigned int compressed_image_size = 0; + + mipmaps = ( ( flags & SOIL_FLAG_MIPMAPS ) && (header->dwpfFlags & PVRTEX_MIPMAP) ) ? header->dwMipMapCount : 0; + + for(mipmap_level = 0; mipmap_level <= mipmaps; width = _MAX(width/2, (unsigned int)1), height = _MAX(height/2, (unsigned int)1), mipmap_level++ ) { + // Do Alpha-swap if needed + cur_texture_ptr = texture_ptr; + + // Load the Texture + /* If the texture is PVRTC then use GLCompressedTexImage2D */ + if( is_compressed_format ) { + /* Calculate how many bytes this MIP level occupies */ + if ((header->dwpfFlags & PVRTEX_PIXELTYPE)==OGL_PVRTC2) { + compressed_image_size = ( _MAX(width, PVRTC2_MIN_TEXWIDTH) * _MAX(height, PVRTC2_MIN_TEXHEIGHT) * header->dwBitCount + 7 ) / 8; + } else {// PVRTC4 case + compressed_image_size = ( _MAX(width, PVRTC4_MIN_TEXWIDTH) * _MAX(height, PVRTC4_MIN_TEXHEIGHT) * header->dwBitCount + 7 ) / 8; + } + + if ( is_compressed_format_supported ) { + /* Load compressed texture data at selected MIP level */ + if ( loading_as_cubemap ) { + soilGlCompressedTexImage2D( SOIL_TEXTURE_CUBE_MAP_POSITIVE_X + i, mipmap_level, PVR_format, width, height, 0, compressed_image_size, cur_texture_ptr ); + } else { + soilGlCompressedTexImage2D( opengl_texture_type, mipmap_level, PVR_format, width, height, 0, compressed_image_size, cur_texture_ptr ); + } + } else { + result_string_pointer = "failed: GPU doesnt support compressed textures"; + } + } else { + /* Load uncompressed texture data at selected MIP level */ + if ( loading_as_cubemap ) { + glTexImage2D( SOIL_TEXTURE_CUBE_MAP_POSITIVE_X + i, mipmap_level, PVR_type, width, height, 0, PVR_type, PVR_format, cur_texture_ptr ); + } else { + glTexImage2D( opengl_texture_type, mipmap_level, PVR_type, width, height, 0, PVR_type, PVR_format, cur_texture_ptr ); + } + } + + if( glGetError() ) { + result_string_pointer = "failed: glCompressedTexImage2D() failed."; + if ( 1 != unpack_aligment ) + { + glPixelStorei(GL_UNPACK_ALIGNMENT, unpack_aligment); + } + return 0; + } + + // offset the texture pointer by one mip-map level + /* PVRTC case */ + if ( is_compressed_format ) { + texture_ptr += compressed_image_size; + } else { + /* New formula that takes into account bit counts inferior to 8 (e.g. 1 bpp) */ + texture_ptr += (width * height * header->dwBitCount + 7) / 8; + } + } + } + #undef _MAX + + if ( 1 != unpack_aligment ) + { + glPixelStorei(GL_UNPACK_ALIGNMENT, unpack_aligment); + } + + if( tex_ID ) + { + /* did I have MIPmaps? */ + if( mipmaps ) + { + /* instruct OpenGL to use the MIPmaps */ + glTexParameteri( opengl_texture_type, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + glTexParameteri( opengl_texture_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR ); + } else + { + /* instruct OpenGL _NOT_ to use the MIPmaps */ + glTexParameteri( opengl_texture_type, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + glTexParameteri( opengl_texture_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + } + + /* does the user want clamping, or wrapping? */ + if( flags & SOIL_FLAG_TEXTURE_REPEATS ) + { + glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_S, GL_REPEAT ); + glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_T, GL_REPEAT ); + glTexParameteri( opengl_texture_type, SOIL_TEXTURE_WRAP_R, GL_REPEAT ); + } else + { + unsigned int clamp_mode = SOIL_CLAMP_TO_EDGE; + /* unsigned int clamp_mode = GL_CLAMP; */ + glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_S, clamp_mode ); + glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_T, clamp_mode ); + glTexParameteri( opengl_texture_type, SOIL_TEXTURE_WRAP_R, clamp_mode ); + } + } + + return tex_ID; +} + +unsigned int SOIL_direct_load_PVR( + const char *filename, + unsigned int reuse_texture_ID, + int flags, + int loading_as_cubemap ) +{ + FILE *f; + unsigned char *buffer; + size_t buffer_length, bytes_read; + unsigned int tex_ID = 0; + /* error checks */ + if( NULL == filename ) + { + result_string_pointer = "NULL filename"; + return 0; + } + f = fopen( filename, "rb" ); + if( NULL == f ) + { + /* the file doesn't seem to exist (or be open-able) */ + result_string_pointer = "Can not find PVR file"; + return 0; + } + fseek( f, 0, SEEK_END ); + buffer_length = ftell( f ); + fseek( f, 0, SEEK_SET ); + buffer = (unsigned char *) malloc( buffer_length ); + if( NULL == buffer ) + { + result_string_pointer = "malloc failed"; + fclose( f ); + return 0; + } + bytes_read = fread( (void*)buffer, 1, buffer_length, f ); + fclose( f ); + if( bytes_read < buffer_length ) + { + /* huh? */ + buffer_length = bytes_read; + } + /* now try to do the loading */ + tex_ID = SOIL_direct_load_PVR_from_memory( + (const unsigned char *const)buffer, (int)buffer_length, reuse_texture_ID, flags, loading_as_cubemap ); SOIL_free_image_data( buffer ); return tex_ID; } +unsigned int SOIL_direct_load_ETC1_from_memory( + const unsigned char *const buffer, + int buffer_length, + unsigned int reuse_texture_ID, + int flags ) +{ + GLuint tex_ID = 0; + PKMHeader* header = (PKMHeader*)buffer; + unsigned int opengl_texture_type = GL_TEXTURE_2D; + unsigned int width; + unsigned int height; + unsigned long compressed_image_size = buffer_length - PKM_HEADER_SIZE; + char *texture_ptr = (char*)buffer + PKM_HEADER_SIZE; + GLint unpack_aligment; + + if ( query_ETC1_capability() != SOIL_CAPABILITY_PRESENT ) { + result_string_pointer = "error: ETC1 not supported. Decompress the texture first."; + return 0; + } + + if ( 0 != strcmp( header->aName, "PKM 10" ) ) { + result_string_pointer = "error: PKM 10 header not found."; + return 0; + } + + width = (header->iWidthMSB << 8) | header->iWidthLSB; + height = (header->iHeightMSB << 8) | header->iHeightLSB; + compressed_image_size = (((width + 3) & ~3) * ((height + 3) & ~3)) >> 1; + + // load the texture up + tex_ID = reuse_texture_ID; + if( tex_ID == 0 ) + { + glGenTextures( 1, &tex_ID ); + } + + glBindTexture( opengl_texture_type, tex_ID ); + + if( glGetError() ) { + result_string_pointer = "failed: glBindTexture() failed."; + return 0; + } + + glGetIntegerv(GL_UNPACK_ALIGNMENT, &unpack_aligment); + if ( 1 != unpack_aligment ) + { + glPixelStorei(GL_UNPACK_ALIGNMENT,1); // Never have row-aligned in headers + } + + soilGlCompressedTexImage2D( opengl_texture_type, 0, SOIL_GL_ETC1_RGB8_OES, width, height, 0, compressed_image_size, texture_ptr ); + + if( glGetError() ) { + result_string_pointer = "failed: glCompressedTexImage2D() failed."; + + if ( 1 != unpack_aligment ) + { + glPixelStorei(GL_UNPACK_ALIGNMENT, unpack_aligment); + } + return 0; + } + + if ( 1 != unpack_aligment ) + { + glPixelStorei(GL_UNPACK_ALIGNMENT, unpack_aligment); + } + + if( tex_ID ) + { + /* No MIPmaps for ETC1 */ + glTexParameteri( opengl_texture_type, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + glTexParameteri( opengl_texture_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + + /* does the user want clamping, or wrapping? */ + if( flags & SOIL_FLAG_TEXTURE_REPEATS ) + { + glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_S, GL_REPEAT ); + glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_T, GL_REPEAT ); + glTexParameteri( opengl_texture_type, SOIL_TEXTURE_WRAP_R, GL_REPEAT ); + } else + { + unsigned int clamp_mode = SOIL_CLAMP_TO_EDGE; + /* unsigned int clamp_mode = GL_CLAMP; */ + glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_S, clamp_mode ); + glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_T, clamp_mode ); + glTexParameteri( opengl_texture_type, SOIL_TEXTURE_WRAP_R, clamp_mode ); + } + } + + return tex_ID; +} + +unsigned int SOIL_direct_load_ETC1(const char *filename, + unsigned int reuse_texture_ID, + int flags ) +{ + FILE *f; + unsigned char *buffer; + size_t buffer_length, bytes_read; + unsigned int tex_ID = 0; + /* error checks */ + if( NULL == filename ) + { + result_string_pointer = "NULL filename"; + return 0; + } + f = fopen( filename, "rb" ); + if( NULL == f ) + { + /* the file doesn't seem to exist (or be open-able) */ + result_string_pointer = "Can not find PVR file"; + return 0; + } + fseek( f, 0, SEEK_END ); + buffer_length = ftell( f ); + fseek( f, 0, SEEK_SET ); + buffer = (unsigned char *) malloc( buffer_length ); + if( NULL == buffer ) + { + result_string_pointer = "malloc failed"; + fclose( f ); + return 0; + } + bytes_read = fread( (void*)buffer, 1, buffer_length, f ); + fclose( f ); + if( bytes_read < buffer_length ) + { + /* huh? */ + buffer_length = bytes_read; + } + /* now try to do the loading */ + tex_ID = SOIL_direct_load_ETC1_from_memory( + (const unsigned char *const)buffer, (int)buffer_length, + reuse_texture_ID, flags ); + SOIL_free_image_data( buffer ); + return tex_ID; +} + int query_NPOT_capability( void ) { /* check for the capability */ @@ -1886,8 +2773,11 @@ int query_NPOT_capability( void ) { /* we haven't yet checked for the capability, do so */ if( - (NULL == strstr( (char const*)glGetString( GL_EXTENSIONS ), + (0 == SOIL_GL_ExtensionSupported( "GL_ARB_texture_non_power_of_two" ) ) + && + (0 == SOIL_GL_ExtensionSupported( + "GL_OES_texture_npot" ) ) ) { /* not there, flag the failure */ @@ -1897,6 +2787,10 @@ int query_NPOT_capability( void ) /* it's there! */ has_NPOT_capability = SOIL_CAPABILITY_PRESENT; } + + #if defined( __emscripten__ ) || defined( EMSCRIPTEN ) + has_NPOT_capability = SOIL_CAPABILITY_PRESENT; + #endif } /* let the user know if we can do non-power-of-two textures or not */ return has_NPOT_capability; @@ -1909,13 +2803,13 @@ int query_tex_rectangle_capability( void ) { /* we haven't yet checked for the capability, do so */ if( - (NULL == strstr( (char const*)glGetString( GL_EXTENSIONS ), + (0 == SOIL_GL_ExtensionSupported( "GL_ARB_texture_rectangle" ) ) && - (NULL == strstr( (char const*)glGetString( GL_EXTENSIONS ), + (0 == SOIL_GL_ExtensionSupported( "GL_EXT_texture_rectangle" ) ) && - (NULL == strstr( (char const*)glGetString( GL_EXTENSIONS ), + (0 == SOIL_GL_ExtensionSupported( "GL_NV_texture_rectangle" ) ) ) { @@ -1938,10 +2832,10 @@ int query_cubemap_capability( void ) { /* we haven't yet checked for the capability, do so */ if( - (NULL == strstr( (char const*)glGetString( GL_EXTENSIONS ), + (0 == SOIL_GL_ExtensionSupported( "GL_ARB_texture_cube_map" ) ) && - (NULL == strstr( (char const*)glGetString( GL_EXTENSIONS ), + (0 == SOIL_GL_ExtensionSupported( "GL_EXT_texture_cube_map" ) ) ) { @@ -1957,59 +2851,43 @@ int query_cubemap_capability( void ) return has_cubemap_capability; } +static P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC get_glCompressedTexImage2D_addr() +{ + /* and find the address of the extension function */ + P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC ext_addr = NULL; + +#if defined( SOIL_PLATFORM_WIN32 ) || defined( SOIL_PLATFORM_OSX ) || defined( SOIL_X11_PLATFORM ) + ext_addr = (P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC)SOIL_GL_GetProcAddress( "glCompressedTexImage2D" ); +#else + ext_addr = (P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC)&glCompressedTexImage2D; +#endif + + return ext_addr; +} + int query_DXT_capability( void ) { /* check for the capability */ if( has_DXT_capability == SOIL_CAPABILITY_UNKNOWN ) { /* we haven't yet checked for the capability, do so */ - if( NULL == strstr( - (char const*)glGetString( GL_EXTENSIONS ), - "GL_EXT_texture_compression_s3tc" ) ) + if ( 0 == SOIL_GL_ExtensionSupported( + "GL_EXT_texture_compression_s3tc" ) && + 0 == SOIL_GL_ExtensionSupported( + "WEBGL_compressed_texture_s3tc ") && + 0 == SOIL_GL_ExtensionSupported( + "WEBKIT_WEBGL_compressed_texture_s3tc") && + 0 == SOIL_GL_ExtensionSupported( + "MOZ_WEBGL_compressed_texture_s3tc" + ) + ) { /* not there, flag the failure */ has_DXT_capability = SOIL_CAPABILITY_NONE; } else { - /* and find the address of the extension function */ - P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC ext_addr = NULL; - #ifdef WIN32 - ext_addr = (P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC) - wglGetProcAddress - ( - "glCompressedTexImage2DARB" - ); - #elif defined(__APPLE__) || defined(__APPLE_CC__) - /* I can't test this Apple stuff! */ - CFBundleRef bundle; - CFURLRef bundleURL = - CFURLCreateWithFileSystemPath( - kCFAllocatorDefault, - CFSTR("/System/Library/Frameworks/OpenGL.framework"), - kCFURLPOSIXPathStyle, - true ); - CFStringRef extensionName = - CFStringCreateWithCString( - kCFAllocatorDefault, - "glCompressedTexImage2DARB", - kCFStringEncodingASCII ); - bundle = CFBundleCreate( kCFAllocatorDefault, bundleURL ); - assert( bundle != NULL ); - ext_addr = (P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC) - CFBundleGetFunctionPointerForName - ( - bundle, extensionName - ); - CFRelease( bundleURL ); - CFRelease( extensionName ); - CFRelease( bundle ); - #else - ext_addr = (P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC) - glXGetProcAddressARB - ( - (const GLubyte *)"glCompressedTexImage2DARB" - ); - #endif + P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC ext_addr = get_glCompressedTexImage2D_addr(); + /* Flag it so no checks needed later */ if( NULL == ext_addr ) { @@ -2032,8 +2910,157 @@ int query_DXT_capability( void ) return has_DXT_capability; } -#ifdef _MSC_VER -#pragma warning(pop) -#else -#pragma GCC diagnostic pop -#endif +int query_PVR_capability( void ) +{ + /* check for the capability */ + if( has_PVR_capability == SOIL_CAPABILITY_UNKNOWN ) + { + /* we haven't yet checked for the capability, do so */ + if (0 == SOIL_GL_ExtensionSupported( + "GL_IMG_texture_compression_pvrtc" ) ) + { + /* not there, flag the failure */ + has_PVR_capability = SOIL_CAPABILITY_NONE; + } else + { + if ( NULL == soilGlCompressedTexImage2D ) { + soilGlCompressedTexImage2D = get_glCompressedTexImage2D_addr(); + } + + /* it's there! */ + has_PVR_capability = SOIL_CAPABILITY_PRESENT; + } + } + /* let the user know if we can do cubemaps or not */ + return has_PVR_capability; +} + +int query_BGRA8888_capability( void ) +{ + /* check for the capability */ + if( has_BGRA8888_capability == SOIL_CAPABILITY_UNKNOWN ) + { + /* we haven't yet checked for the capability, do so */ + if (0 == SOIL_GL_ExtensionSupported( + "GL_IMG_texture_format_BGRA8888" ) ) + { + /* not there, flag the failure */ + has_BGRA8888_capability = SOIL_CAPABILITY_NONE; + } else + { + /* it's there! */ + has_BGRA8888_capability = SOIL_CAPABILITY_PRESENT; + } + } + /* let the user know if we can do cubemaps or not */ + return has_BGRA8888_capability; +} + +int query_sRGB_capability( void ) +{ + if ( has_sRGB_capability == SOIL_CAPABILITY_UNKNOWN ) + { + if (0 == SOIL_GL_ExtensionSupported( + "GL_EXT_texture_sRGB" ) + && + 0 == SOIL_GL_ExtensionSupported( + "GL_EXT_sRGB" ) + && + 0 == SOIL_GL_ExtensionSupported( + "EXT_sRGB" ) ) + { + has_sRGB_capability = SOIL_CAPABILITY_NONE; + } else + { + has_sRGB_capability = SOIL_CAPABILITY_PRESENT; + } + } + + return has_sRGB_capability; +} + +int query_ETC1_capability( void ) +{ + /* check for the capability */ + if( has_ETC1_capability == SOIL_CAPABILITY_UNKNOWN ) + { + /* we haven't yet checked for the capability, do so */ + if (0 == SOIL_GL_ExtensionSupported( + "GL_OES_compressed_ETC1_RGB8_texture" ) ) + { + /* not there, flag the failure */ + has_ETC1_capability = SOIL_CAPABILITY_NONE; + } else + { + if ( NULL == soilGlCompressedTexImage2D ) { + soilGlCompressedTexImage2D = get_glCompressedTexImage2D_addr(); + } + + /* it's there! */ + has_ETC1_capability = SOIL_CAPABILITY_PRESENT; + } + } + /* let the user know if we can do cubemaps or not */ + return has_ETC1_capability; +} + +int query_gen_mipmap_capability( void ) +{ + /* check for the capability */ + P_SOIL_GLGENERATEMIPMAPPROC ext_addr = NULL; + + if( has_gen_mipmap_capability == SOIL_CAPABILITY_UNKNOWN ) + { + if ( 0 == SOIL_GL_ExtensionSupported( + "GL_ARB_framebuffer_object" ) + && + 0 == SOIL_GL_ExtensionSupported( + "GL_EXT_framebuffer_object" ) + && 0 == SOIL_GL_ExtensionSupported( + "GL_OES_framebuffer_object" ) + ) + { + /* not there, flag the failure */ + has_gen_mipmap_capability = SOIL_CAPABILITY_NONE; + } + else + { + #if !defined( SOIL_GLES1 ) && !defined( SOIL_GLES2 ) + + ext_addr = (P_SOIL_GLGENERATEMIPMAPPROC)SOIL_GL_GetProcAddress("glGenerateMipmap"); + + if(ext_addr == NULL) + { + ext_addr = (P_SOIL_GLGENERATEMIPMAPPROC)SOIL_GL_GetProcAddress("glGenerateMipmapEXT"); + } + + #elif !defined( SOIL_NO_EGL ) + + ext_addr = (P_SOIL_GLGENERATEMIPMAPPROC)SOIL_GL_GetProcAddress("glGenerateMipmapOES"); + + if(ext_addr == NULL) + { + ext_addr = (P_SOIL_GLGENERATEMIPMAPPROC)SOIL_GL_GetProcAddress("glGenerateMipmap"); + } + + #elif defined( SOIL_GLES2 ) + ext_addr = &glGenerateMipmap; + #else /** SOIL_GLES1 */ + ext_addr = &glGenerateMipmapOES; + #endif + } + + if(ext_addr == NULL) + { + /* this should never happen */ + has_gen_mipmap_capability = SOIL_CAPABILITY_NONE; + } else + { + /* it's there! */ + has_gen_mipmap_capability = SOIL_CAPABILITY_PRESENT; + soilGlGenerateMipmap = ext_addr; + } + } + + return has_gen_mipmap_capability; +} diff --git a/lib/soil/SOIL.h b/lib/SOIL2/SOIL2.h similarity index 84% rename from lib/soil/SOIL.h rename to lib/SOIL2/SOIL2.h index 43f634f86..eace403cf 100644 --- a/lib/soil/SOIL.h +++ b/lib/SOIL2/SOIL2.h @@ -1,10 +1,12 @@ /** - @mainpage SOIL + @mainpage SOIL2 - Jonathan Dummer + Fork by Martin Lucas Golini + + Original author Jonathan Dummer 2007-07-26-10.36 - Simple OpenGL Image Library + Simple OpenGL Image Library 2 A tiny c library for uploading images as textures into OpenGL. Also saving and @@ -21,8 +23,11 @@ - BMP load & save - TGA load & save - DDS load & save - - PNG load - - JPG load + - PNG load & save + - JPG load & save + - PSD load + - HDR load + - PIC load OpenGL Texture Features: - resample to power-of-two sizes @@ -87,10 +92,11 @@ enum SOIL_FLAG_MULTIPLY_ALPHA: for using (GL_ONE,GL_ONE_MINUS_SRC_ALPHA) blending SOIL_FLAG_INVERT_Y: flip the image vertically SOIL_FLAG_COMPRESS_TO_DXT: if the card can display them, will convert RGB to DXT1, RGBA to DXT5 - SOIL_FLAG_DDS_LOAD_DIRECT: will load DDS files directly without _ANY_ additional processing + SOIL_FLAG_DDS_LOAD_DIRECT: will load DDS files directly without _ANY_ additional processing ( if supported ) SOIL_FLAG_NTSC_SAFE_RGB: clamps RGB components to the range [16,235] SOIL_FLAG_CoCg_Y: Google YCoCg; RGB=>CoYCg, RGBA=>CoCgAY SOIL_FLAG_TEXTURE_RECTANGE: uses ARB_texture_rectangle ; pixel indexed & no repeat or MIPmaps or cubemaps + SOIL_FLAG_PVR_LOAD_DIRECT: will load PVR files directly without _ANY_ additional processing ( if supported ) **/ enum { @@ -103,7 +109,11 @@ enum SOIL_FLAG_DDS_LOAD_DIRECT = 64, SOIL_FLAG_NTSC_SAFE_RGB = 128, SOIL_FLAG_CoCg_Y = 256, - SOIL_FLAG_TEXTURE_RECTANGLE = 512 + SOIL_FLAG_TEXTURE_RECTANGLE = 512, + SOIL_FLAG_PVR_LOAD_DIRECT = 1024, + SOIL_FLAG_ETC1_LOAD_DIRECT = 2048, + SOIL_FLAG_GL_MIPMAPS = 4096, + SOIL_FLAG_SRGB_COLOR_SPACE = 8192 }; /** @@ -111,12 +121,15 @@ enum (TGA supports uncompressed RGB / RGBA) (BMP supports uncompressed RGB) (DDS supports DXT1 and DXT5) + (PNG supports RGB / RGBA) **/ enum { SOIL_SAVE_TYPE_TGA = 0, SOIL_SAVE_TYPE_BMP = 1, - SOIL_SAVE_TYPE_DDS = 2 + SOIL_SAVE_TYPE_PNG = 2, + SOIL_SAVE_TYPE_DDS = 3, + SOIL_SAVE_TYPE_JPG = 4 }; /** @@ -305,8 +318,8 @@ unsigned int Creates a 2D OpenGL texture from raw image data. Note that the raw data is _NOT_ freed after the upload (so the user can load various versions). \param data the raw data to be uploaded as an OpenGL texture - \param width the width of the image in pixels - \param height the height of the image in pixels + \param width the pointer of the width of the image in pixels ( if the texture size change, width will be overrided with the new width ) + \param height the pointer of the height of the image in pixels ( if the texture size change, height will be overrided with the new height ) \param channels the number of channels: 1-luminous, 2-luminous/alpha, 3-RGB, 4-RGBA \param reuse_texture_ID 0-generate a new texture ID, otherwise reuse the texture ID (overwriting the old texture) \param flags can be any of SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | SOIL_FLAG_TEXTURE_REPEATS | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT @@ -316,7 +329,7 @@ unsigned int SOIL_create_OGL_texture ( const unsigned char *const data, - int width, int height, int channels, + int *width, int *height, int channels, unsigned int reuse_texture_ID, unsigned int flags ); @@ -392,8 +405,19 @@ unsigned char* /** Saves an image from an array of unsigned chars (RGBA) to disk + \param quality parameter only used for SOIL_SAVE_TYPE_JPG files, values accepted between 0 and 100. \return 0 if failed, otherwise returns 1 **/ +int + SOIL_save_image_quality + ( + const char *filename, + int image_type, + int width, int height, int channels, + const unsigned char *const data, + int quality + ); + int SOIL_save_image ( @@ -425,6 +449,60 @@ const char* void ); +/** @return The address of the GL function proc, or NULL if the function is not found. */ +void * + SOIL_GL_GetProcAddress + ( + const char *proc + ); + +/** @return 1 if an OpenGL extension is supported for the current context, 0 otherwise. */ +int + SOIL_GL_ExtensionSupported + ( + const char *extension + ); + +/** Loads the DDS texture directly to the GPU memory ( if supported ) */ +unsigned int SOIL_direct_load_DDS( + const char *filename, + unsigned int reuse_texture_ID, + int flags, + int loading_as_cubemap ); + +/** Loads the DDS texture directly to the GPU memory ( if supported ) */ +unsigned int SOIL_direct_load_DDS_from_memory( + const unsigned char *const buffer, + int buffer_length, + unsigned int reuse_texture_ID, + int flags, + int loading_as_cubemap ); + +/** Loads the PVR texture directly to the GPU memory ( if supported ) */ +unsigned int SOIL_direct_load_PVR( + const char *filename, + unsigned int reuse_texture_ID, + int flags, + int loading_as_cubemap ); + +/** Loads the PVR texture directly to the GPU memory ( if supported ) */ +unsigned int SOIL_direct_load_PVR_from_memory( + const unsigned char *const buffer, + int buffer_length, + unsigned int reuse_texture_ID, + int flags, + int loading_as_cubemap ); + +/** Loads the PVR texture directly to the GPU memory ( if supported ) */ +unsigned int SOIL_direct_load_ETC1(const char *filename, + unsigned int reuse_texture_ID, + int flags ); + +/** Loads the PVR texture directly to the GPU memory ( if supported ) */ +unsigned int SOIL_direct_load_ETC1_from_memory(const unsigned char *const buffer, + int buffer_length, + unsigned int reuse_texture_ID, + int flags ); #ifdef __cplusplus } diff --git a/lib/SOIL2/etc1_utils.c b/lib/SOIL2/etc1_utils.c new file mode 100644 index 000000000..1d10f0e2b --- /dev/null +++ b/lib/SOIL2/etc1_utils.c @@ -0,0 +1,680 @@ +// Copyright 2009 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "etc1_utils.h" + +#include + +/* From http://www.khronos.org/registry/gles/extensions/OES/OES_compressed_ETC1_RGB8_texture.txt + + The number of bits that represent a 4x4 texel block is 64 bits if + is given by ETC1_RGB8_OES. + + The data for a block is a number of bytes, + + {q0, q1, q2, q3, q4, q5, q6, q7} + + where byte q0 is located at the lowest memory address and q7 at + the highest. The 64 bits specifying the block is then represented + by the following 64 bit integer: + + int64bit = 256*(256*(256*(256*(256*(256*(256*q0+q1)+q2)+q3)+q4)+q5)+q6)+q7; + + ETC1_RGB8_OES: + + a) bit layout in bits 63 through 32 if diffbit = 0 + + 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 + ----------------------------------------------- + | base col1 | base col2 | base col1 | base col2 | + | R1 (4bits)| R2 (4bits)| G1 (4bits)| G2 (4bits)| + ----------------------------------------------- + + 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + --------------------------------------------------- + | base col1 | base col2 | table | table |diff|flip| + | B1 (4bits)| B2 (4bits)| cw 1 | cw 2 |bit |bit | + --------------------------------------------------- + + + b) bit layout in bits 63 through 32 if diffbit = 1 + + 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 + ----------------------------------------------- + | base col1 | dcol 2 | base col1 | dcol 2 | + | R1' (5 bits) | dR2 | G1' (5 bits) | dG2 | + ----------------------------------------------- + + 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + --------------------------------------------------- + | base col 1 | dcol 2 | table | table |diff|flip| + | B1' (5 bits) | dB2 | cw 1 | cw 2 |bit |bit | + --------------------------------------------------- + + + c) bit layout in bits 31 through 0 (in both cases) + + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 + ----------------------------------------------- + | most significant pixel index bits | + | p| o| n| m| l| k| j| i| h| g| f| e| d| c| b| a| + ----------------------------------------------- + + 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + -------------------------------------------------- + | least significant pixel index bits | + | p| o| n| m| l| k| j| i| h| g| f| e| d| c | b | a | + -------------------------------------------------- + + + Add table 3.17.2: Intensity modifier sets for ETC1 compressed textures: + + table codeword modifier table + ------------------ ---------------------- + 0 -8 -2 2 8 + 1 -17 -5 5 17 + 2 -29 -9 9 29 + 3 -42 -13 13 42 + 4 -60 -18 18 60 + 5 -80 -24 24 80 + 6 -106 -33 33 106 + 7 -183 -47 47 183 + + + Add table 3.17.3 Mapping from pixel index values to modifier values for + ETC1 compressed textures: + + pixel index value + --------------- + msb lsb resulting modifier value + ----- ----- ------------------------- + 1 1 -b (large negative value) + 1 0 -a (small negative value) + 0 0 a (small positive value) + 0 1 b (large positive value) + + + */ + +static const int kModifierTable[] = { +/* 0 */2, 8, -2, -8, +/* 1 */5, 17, -5, -17, +/* 2 */9, 29, -9, -29, +/* 3 */13, 42, -13, -42, +/* 4 */18, 60, -18, -60, +/* 5 */24, 80, -24, -80, +/* 6 */33, 106, -33, -106, +/* 7 */47, 183, -47, -183 }; + +static const int kLookup[8] = { 0, 1, 2, 3, -4, -3, -2, -1 }; + +static inline etc1_byte clamp(int x) { + return (etc1_byte) (x >= 0 ? (x < 255 ? x : 255) : 0); +} + +static +inline int convert4To8(int b) { + int c = b & 0xf; + return (c << 4) | c; +} + +static +inline int convert5To8(int b) { + int c = b & 0x1f; + return (c << 3) | (c >> 2); +} + +static +inline int convert6To8(int b) { + int c = b & 0x3f; + return (c << 2) | (c >> 4); +} + +static +inline int divideBy255(int d) { + return (d + 128 + (d >> 8)) >> 8; +} + +static +inline int convert8To4(int b) { + //int c = b & 0xff; + return divideBy255(b * 15); +} + +static +inline int convert8To5(int b) { + //int c = b & 0xff; + return divideBy255(b * 31); +} + +static +inline int convertDiff(int base, int diff) { + return convert5To8((0x1f & base) + kLookup[0x7 & diff]); +} + +static +void decode_subblock(etc1_byte* pOut, int r, int g, int b, const int* table, + etc1_uint32 low, etc1_bool second, etc1_bool flipped) { + int baseX = 0; + int baseY = 0; + int i; + + if (second) { + if (flipped) { + baseY = 2; + } else { + baseX = 2; + } + } + for (i = 0; i < 8; i++) { + int x, y; + if (flipped) { + x = baseX + (i >> 1); + y = baseY + (i & 1); + } else { + x = baseX + (i >> 2); + y = baseY + (i & 3); + } + int k = y + (x * 4); + int offset = ((low >> k) & 1) | ((low >> (k + 15)) & 2); + int delta = table[offset]; + etc1_byte* q = pOut + 3 * (x + 4 * y); + *q++ = clamp(r + delta); + *q++ = clamp(g + delta); + *q++ = clamp(b + delta); + } +} + +// Input is an ETC1 compressed version of the data. +// Output is a 4 x 4 square of 3-byte pixels in form R, G, B + +void etc1_decode_block(const etc1_byte* pIn, etc1_byte* pOut) { + etc1_uint32 high = (pIn[0] << 24) | (pIn[1] << 16) | (pIn[2] << 8) | pIn[3]; + etc1_uint32 low = (pIn[4] << 24) | (pIn[5] << 16) | (pIn[6] << 8) | pIn[7]; + int r1, r2, g1, g2, b1, b2; + if (high & 2) { + // differential + int rBase = high >> 27; + int gBase = high >> 19; + int bBase = high >> 11; + r1 = convert5To8(rBase); + r2 = convertDiff(rBase, high >> 24); + g1 = convert5To8(gBase); + g2 = convertDiff(gBase, high >> 16); + b1 = convert5To8(bBase); + b2 = convertDiff(bBase, high >> 8); + } else { + // not differential + r1 = convert4To8(high >> 28); + r2 = convert4To8(high >> 24); + g1 = convert4To8(high >> 20); + g2 = convert4To8(high >> 16); + b1 = convert4To8(high >> 12); + b2 = convert4To8(high >> 8); + } + int tableIndexA = 7 & (high >> 5); + int tableIndexB = 7 & (high >> 2); + const int* tableA = kModifierTable + tableIndexA * 4; + const int* tableB = kModifierTable + tableIndexB * 4; + etc1_bool flipped = (high & 1) != 0; + decode_subblock(pOut, r1, g1, b1, tableA, low, 0, flipped); + decode_subblock(pOut, r2, g2, b2, tableB, low, 1, flipped); +} + +typedef struct { + etc1_uint32 high; + etc1_uint32 low; + etc1_uint32 score; // Lower is more accurate +} etc_compressed; + +static +inline void take_best(etc_compressed* a, const etc_compressed* b) { + if (a->score > b->score) { + *a = *b; + } +} + +static +void etc_average_colors_subblock(const etc1_byte* pIn, etc1_uint32 inMask, + etc1_byte* pColors, etc1_bool flipped, etc1_bool second) { + int r = 0; + int g = 0; + int b = 0; + int y, x; + + if (flipped) { + int by = 0; + if (second) { + by = 2; + } + for ( y = 0; y < 2; y++) { + int yy = by + y; + for ( x = 0; x < 4; x++) { + int i = x + 4 * yy; + if (inMask & (1 << i)) { + const etc1_byte* p = pIn + i * 3; + r += *(p++); + g += *(p++); + b += *(p++); + } + } + } + } else { + int bx = 0; + if (second) { + bx = 2; + } + for ( y = 0; y < 4; y++) { + for ( x = 0; x < 2; x++) { + int xx = bx + x; + int i = xx + 4 * y; + if (inMask & (1 << i)) { + const etc1_byte* p = pIn + i * 3; + r += *(p++); + g += *(p++); + b += *(p++); + } + } + } + } + pColors[0] = (etc1_byte)((r + 4) >> 3); + pColors[1] = (etc1_byte)((g + 4) >> 3); + pColors[2] = (etc1_byte)((b + 4) >> 3); +} + +static +inline int square(int x) { + return x * x; +} + +static etc1_uint32 chooseModifier(const etc1_byte* pBaseColors, + const etc1_byte* pIn, etc1_uint32 *pLow, int bitIndex, + const int* pModifierTable) { + etc1_uint32 bestScore = ~0; + int bestIndex = 0; + int pixelR = pIn[0]; + int pixelG = pIn[1]; + int pixelB = pIn[2]; + int r = pBaseColors[0]; + int g = pBaseColors[1]; + int b = pBaseColors[2]; + int i; + for ( i = 0; i < 4; i++) { + int modifier = pModifierTable[i]; + int decodedG = clamp(g + modifier); + etc1_uint32 score = (etc1_uint32) (6 * square(decodedG - pixelG)); + if (score >= bestScore) { + continue; + } + int decodedR = clamp(r + modifier); + score += (etc1_uint32) (3 * square(decodedR - pixelR)); + if (score >= bestScore) { + continue; + } + int decodedB = clamp(b + modifier); + score += (etc1_uint32) square(decodedB - pixelB); + if (score < bestScore) { + bestScore = score; + bestIndex = i; + } + } + etc1_uint32 lowMask = (((bestIndex >> 1) << 16) | (bestIndex & 1)) + << bitIndex; + *pLow |= lowMask; + return bestScore; +} + +static +void etc_encode_subblock_helper(const etc1_byte* pIn, etc1_uint32 inMask, + etc_compressed* pCompressed, etc1_bool flipped, etc1_bool second, + const etc1_byte* pBaseColors, const int* pModifierTable) { + int score = pCompressed->score; + int y, x; + if (flipped) { + int by = 0; + if (second) { + by = 2; + } + for ( y = 0; y < 2; y++) { + int yy = by + y; + for ( x = 0; x < 4; x++) { + int i = x + 4 * yy; + if (inMask & (1 << i)) { + score += chooseModifier(pBaseColors, pIn + i * 3, + &pCompressed->low, yy + x * 4, pModifierTable); + } + } + } + } else { + int bx = 0; + if (second) { + bx = 2; + } + for ( y = 0; y < 4; y++) { + for ( x = 0; x < 2; x++) { + int xx = bx + x; + int i = xx + 4 * y; + if (inMask & (1 << i)) { + score += chooseModifier(pBaseColors, pIn + i * 3, + &pCompressed->low, y + xx * 4, pModifierTable); + } + } + } + } + pCompressed->score = score; +} + +static etc1_bool inRange4bitSigned(int color) { + return color >= -4 && color <= 3; +} + +static void etc_encodeBaseColors(etc1_byte* pBaseColors, + const etc1_byte* pColors, etc_compressed* pCompressed) { + int r1, g1, b1, r2 = 0, g2 = 0, b2 = 0; // 8 bit base colors for sub-blocks + etc1_bool differential; + { + int r51 = convert8To5(pColors[0]); + int g51 = convert8To5(pColors[1]); + int b51 = convert8To5(pColors[2]); + int r52 = convert8To5(pColors[3]); + int g52 = convert8To5(pColors[4]); + int b52 = convert8To5(pColors[5]); + + r1 = convert5To8(r51); + g1 = convert5To8(g51); + b1 = convert5To8(b51); + + int dr = r52 - r51; + int dg = g52 - g51; + int db = b52 - b51; + + differential = inRange4bitSigned(dr) && inRange4bitSigned(dg) + && inRange4bitSigned(db); + if (differential) { + r2 = convert5To8(r51 + dr); + g2 = convert5To8(g51 + dg); + b2 = convert5To8(b51 + db); + pCompressed->high |= (r51 << 27) | ((7 & dr) << 24) | (g51 << 19) + | ((7 & dg) << 16) | (b51 << 11) | ((7 & db) << 8) | 2; + } + } + + if (!differential) { + int r41 = convert8To4(pColors[0]); + int g41 = convert8To4(pColors[1]); + int b41 = convert8To4(pColors[2]); + int r42 = convert8To4(pColors[3]); + int g42 = convert8To4(pColors[4]); + int b42 = convert8To4(pColors[5]); + r1 = convert4To8(r41); + g1 = convert4To8(g41); + b1 = convert4To8(b41); + r2 = convert4To8(r42); + g2 = convert4To8(g42); + b2 = convert4To8(b42); + pCompressed->high |= (r41 << 28) | (r42 << 24) | (g41 << 20) | (g42 + << 16) | (b41 << 12) | (b42 << 8); + } + pBaseColors[0] = r1; + pBaseColors[1] = g1; + pBaseColors[2] = b1; + pBaseColors[3] = r2; + pBaseColors[4] = g2; + pBaseColors[5] = b2; +} + +static +void etc_encode_block_helper(const etc1_byte* pIn, etc1_uint32 inMask, + const etc1_byte* pColors, etc_compressed* pCompressed, etc1_bool flipped) { + int i; + + pCompressed->score = ~0; + pCompressed->high = (flipped ? 1 : 0); + pCompressed->low = 0; + + etc1_byte pBaseColors[6]; + + etc_encodeBaseColors(pBaseColors, pColors, pCompressed); + + int originalHigh = pCompressed->high; + + const int* pModifierTable = kModifierTable; + for ( i = 0; i < 8; i++, pModifierTable += 4) { + etc_compressed temp; + temp.score = 0; + temp.high = originalHigh | (i << 5); + temp.low = 0; + etc_encode_subblock_helper(pIn, inMask, &temp, flipped, 0, + pBaseColors, pModifierTable); + take_best(pCompressed, &temp); + } + pModifierTable = kModifierTable; + etc_compressed firstHalf = *pCompressed; + for ( i = 0; i < 8; i++, pModifierTable += 4) { + etc_compressed temp; + temp.score = firstHalf.score; + temp.high = firstHalf.high | (i << 2); + temp.low = firstHalf.low; + etc_encode_subblock_helper(pIn, inMask, &temp, flipped, 1, + pBaseColors + 3, pModifierTable); + if (i == 0) { + *pCompressed = temp; + } else { + take_best(pCompressed, &temp); + } + } +} + +static void writeBigEndian(etc1_byte* pOut, etc1_uint32 d) { + pOut[0] = (etc1_byte)(d >> 24); + pOut[1] = (etc1_byte)(d >> 16); + pOut[2] = (etc1_byte)(d >> 8); + pOut[3] = (etc1_byte) d; +} + +// Input is a 4 x 4 square of 3-byte pixels in form R, G, B +// inmask is a 16-bit mask where bit (1 << (x + y * 4)) tells whether the corresponding (x,y) +// pixel is valid or not. Invalid pixel color values are ignored when compressing. +// Output is an ETC1 compressed version of the data. + +void etc1_encode_block(const etc1_byte* pIn, etc1_uint32 inMask, + etc1_byte* pOut) { + etc1_byte colors[6]; + etc1_byte flippedColors[6]; + etc_average_colors_subblock(pIn, inMask, colors, 0, 0); + etc_average_colors_subblock(pIn, inMask, colors + 3, 0, 1); + etc_average_colors_subblock(pIn, inMask, flippedColors, 1, 0); + etc_average_colors_subblock(pIn, inMask, flippedColors + 3, 1, 1); + + etc_compressed a, b; + etc_encode_block_helper(pIn, inMask, colors, &a, 0); + etc_encode_block_helper(pIn, inMask, flippedColors, &b, 1); + take_best(&a, &b); + writeBigEndian(pOut, a.high); + writeBigEndian(pOut + 4, a.low); +} + +// Return the size of the encoded image data (does not include size of PKM header). + +etc1_uint32 etc1_get_encoded_data_size(etc1_uint32 width, etc1_uint32 height) { + return (((width + 3) & ~3) * ((height + 3) & ~3)) >> 1; +} + +// Encode an entire image. +// pIn - pointer to the image data. Formatted such that the Red component of +// pixel (x,y) is at pIn + pixelSize * x + stride * y + redOffset; +// pOut - pointer to encoded data. Must be large enough to store entire encoded image. + +int etc1_encode_image(const etc1_byte* pIn, etc1_uint32 width, etc1_uint32 height, + etc1_uint32 pixelSize, etc1_uint32 stride, etc1_byte* pOut) { + if (pixelSize < 2 || pixelSize > 3) { + return -1; + } + static const unsigned short kYMask[] = { 0x0, 0xf, 0xff, 0xfff, 0xffff }; + static const unsigned short kXMask[] = { 0x0, 0x1111, 0x3333, 0x7777, + 0xffff }; + etc1_byte block[ETC1_DECODED_BLOCK_SIZE]; + etc1_byte encoded[ETC1_ENCODED_BLOCK_SIZE]; + etc1_uint32 y, x, cy, cx; + + etc1_uint32 encodedWidth = (width + 3) & ~3; + etc1_uint32 encodedHeight = (height + 3) & ~3; + + for ( y = 0; y < encodedHeight; y += 4) { + etc1_uint32 yEnd = height - y; + if (yEnd > 4) { + yEnd = 4; + } + int ymask = kYMask[yEnd]; + for ( x = 0; x < encodedWidth; x += 4) { + etc1_uint32 xEnd = width - x; + if (xEnd > 4) { + xEnd = 4; + } + int mask = ymask & kXMask[xEnd]; + for ( cy = 0; cy < yEnd; cy++) { + etc1_byte* q = block + (cy * 4) * 3; + const etc1_byte* p = pIn + pixelSize * x + stride * (y + cy); + if (pixelSize == 3) { + memcpy(q, p, xEnd * 3); + } else { + for ( cx = 0; cx < xEnd; cx++) { + int pixel = (p[1] << 8) | p[0]; + *q++ = convert5To8(pixel >> 11); + *q++ = convert6To8(pixel >> 5); + *q++ = convert5To8(pixel); + p += pixelSize; + } + } + } + etc1_encode_block(block, mask, encoded); + memcpy(pOut, encoded, sizeof(encoded)); + pOut += sizeof(encoded); + } + } + return 0; +} + +// Decode an entire image. +// pIn - pointer to encoded data. +// pOut - pointer to the image data. Will be written such that the Red component of +// pixel (x,y) is at pIn + pixelSize * x + stride * y + redOffset. Must be +// large enough to store entire image. + + +int etc1_decode_image(const etc1_byte* pIn, etc1_byte* pOut, + etc1_uint32 width, etc1_uint32 height, + etc1_uint32 pixelSize, etc1_uint32 stride) { + if (pixelSize < 2 || pixelSize > 3) { + return -1; + } + etc1_byte block[ETC1_DECODED_BLOCK_SIZE]; + + etc1_uint32 encodedWidth = (width + 3) & ~3; + etc1_uint32 encodedHeight = (height + 3) & ~3; + + etc1_uint32 y, x, cy, cx; + + for ( y = 0; y < encodedHeight; y += 4) { + etc1_uint32 yEnd = height - y; + if (yEnd > 4) { + yEnd = 4; + } + for ( x = 0; x < encodedWidth; x += 4) { + etc1_uint32 xEnd = width - x; + if (xEnd > 4) { + xEnd = 4; + } + etc1_decode_block(pIn, block); + pIn += ETC1_ENCODED_BLOCK_SIZE; + for ( cy = 0; cy < yEnd; cy++) { + const etc1_byte* q = block + (cy * 4) * 3; + etc1_byte* p = pOut + pixelSize * x + stride * (y + cy); + if (pixelSize == 3) { + memcpy(p, q, xEnd * 3); + } else { + for ( cx = 0; cx < xEnd; cx++) { + etc1_byte r = *q++; + etc1_byte g = *q++; + etc1_byte b = *q++; + etc1_uint32 pixel = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3); + *p++ = (etc1_byte) pixel; + *p++ = (etc1_byte) (pixel >> 8); + } + } + } + } + } + return 0; +} + +static const char kMagic[] = { 'P', 'K', 'M', ' ', '1', '0' }; + +static const etc1_uint32 ETC1_PKM_FORMAT_OFFSET = 6; +static const etc1_uint32 ETC1_PKM_ENCODED_WIDTH_OFFSET = 8; +static const etc1_uint32 ETC1_PKM_ENCODED_HEIGHT_OFFSET = 10; +static const etc1_uint32 ETC1_PKM_WIDTH_OFFSET = 12; +static const etc1_uint32 ETC1_PKM_HEIGHT_OFFSET = 14; + +static const etc1_uint32 ETC1_RGB_NO_MIPMAPS = 0; + +static void writeBEUint16(etc1_byte* pOut, etc1_uint32 data) { + pOut[0] = (etc1_byte) (data >> 8); + pOut[1] = (etc1_byte) data; +} + +static etc1_uint32 readBEUint16(const etc1_byte* pIn) { + return (pIn[0] << 8) | pIn[1]; +} + +// Format a PKM header + +void etc1_pkm_format_header(etc1_byte* pHeader, etc1_uint32 width, etc1_uint32 height) { + memcpy(pHeader, kMagic, sizeof(kMagic)); + etc1_uint32 encodedWidth = (width + 3) & ~3; + etc1_uint32 encodedHeight = (height + 3) & ~3; + writeBEUint16(pHeader + ETC1_PKM_FORMAT_OFFSET, ETC1_RGB_NO_MIPMAPS); + writeBEUint16(pHeader + ETC1_PKM_ENCODED_WIDTH_OFFSET, encodedWidth); + writeBEUint16(pHeader + ETC1_PKM_ENCODED_HEIGHT_OFFSET, encodedHeight); + writeBEUint16(pHeader + ETC1_PKM_WIDTH_OFFSET, width); + writeBEUint16(pHeader + ETC1_PKM_HEIGHT_OFFSET, height); +} + +// Check if a PKM header is correctly formatted. + +etc1_bool etc1_pkm_is_valid(const etc1_byte* pHeader) { + if (memcmp(pHeader, kMagic, sizeof(kMagic))) { + return 0; + } + etc1_uint32 format = readBEUint16(pHeader + ETC1_PKM_FORMAT_OFFSET); + etc1_uint32 encodedWidth = readBEUint16(pHeader + ETC1_PKM_ENCODED_WIDTH_OFFSET); + etc1_uint32 encodedHeight = readBEUint16(pHeader + ETC1_PKM_ENCODED_HEIGHT_OFFSET); + etc1_uint32 width = readBEUint16(pHeader + ETC1_PKM_WIDTH_OFFSET); + etc1_uint32 height = readBEUint16(pHeader + ETC1_PKM_HEIGHT_OFFSET); + return format == ETC1_RGB_NO_MIPMAPS && + encodedWidth >= width && encodedWidth - width < 4 && + encodedHeight >= height && encodedHeight - height < 4; +} + +// Read the image width from a PKM header + +etc1_uint32 etc1_pkm_get_width(const etc1_byte* pHeader) { + return readBEUint16(pHeader + ETC1_PKM_WIDTH_OFFSET); +} + +// Read the image height from a PKM header + +etc1_uint32 etc1_pkm_get_height(const etc1_byte* pHeader){ + return readBEUint16(pHeader + ETC1_PKM_HEIGHT_OFFSET); +} diff --git a/lib/SOIL2/etc1_utils.h b/lib/SOIL2/etc1_utils.h new file mode 100644 index 000000000..4bee00c24 --- /dev/null +++ b/lib/SOIL2/etc1_utils.h @@ -0,0 +1,106 @@ +// Copyright 2009 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef __etc1_h__ +#define __etc1_h__ + +#define ETC1_ENCODED_BLOCK_SIZE 8 +#define ETC1_DECODED_BLOCK_SIZE 48 + +#ifndef ETC1_RGB8_OES +#define ETC1_RGB8_OES 0x8D64 +#endif + +typedef unsigned char etc1_byte; +typedef int etc1_bool; +typedef unsigned int etc1_uint32; + +#ifdef __cplusplus +extern "C" { +#endif + +// Encode a block of pixels. +// +// pIn is a pointer to a ETC_DECODED_BLOCK_SIZE array of bytes that represent a +// 4 x 4 square of 3-byte pixels in form R, G, B. Byte (3 * (x + 4 * y) is the R +// value of pixel (x, y). +// +// validPixelMask is a 16-bit mask where bit (1 << (x + y * 4)) indicates whether +// the corresponding (x,y) pixel is valid. Invalid pixel color values are ignored when compressing. +// +// pOut is an ETC1 compressed version of the data. + +void etc1_encode_block(const etc1_byte* pIn, etc1_uint32 validPixelMask, etc1_byte* pOut); + +// Decode a block of pixels. +// +// pIn is an ETC1 compressed version of the data. +// +// pOut is a pointer to a ETC_DECODED_BLOCK_SIZE array of bytes that represent a +// 4 x 4 square of 3-byte pixels in form R, G, B. Byte (3 * (x + 4 * y) is the R +// value of pixel (x, y). + +void etc1_decode_block(const etc1_byte* pIn, etc1_byte* pOut); + +// Return the size of the encoded image data (does not include size of PKM header). + +etc1_uint32 etc1_get_encoded_data_size(etc1_uint32 width, etc1_uint32 height); + +// Encode an entire image. +// pIn - pointer to the image data. Formatted such that +// pixel (x,y) is at pIn + pixelSize * x + stride * y; +// pOut - pointer to encoded data. Must be large enough to store entire encoded image. +// pixelSize can be 2 or 3. 2 is an GL_UNSIGNED_SHORT_5_6_5 image, 3 is a GL_BYTE RGB image. +// returns non-zero if there is an error. + +int etc1_encode_image(const etc1_byte* pIn, etc1_uint32 width, etc1_uint32 height, + etc1_uint32 pixelSize, etc1_uint32 stride, etc1_byte* pOut); + +// Decode an entire image. +// pIn - pointer to encoded data. +// pOut - pointer to the image data. Will be written such that +// pixel (x,y) is at pIn + pixelSize * x + stride * y. Must be +// large enough to store entire image. +// pixelSize can be 2 or 3. 2 is an GL_UNSIGNED_SHORT_5_6_5 image, 3 is a GL_BYTE RGB image. +// returns non-zero if there is an error. + +int etc1_decode_image(const etc1_byte* pIn, etc1_byte* pOut, + etc1_uint32 width, etc1_uint32 height, + etc1_uint32 pixelSize, etc1_uint32 stride); + +// Size of a PKM header, in bytes. + +#define ETC_PKM_HEADER_SIZE 16 + +// Format a PKM header + +void etc1_pkm_format_header(etc1_byte* pHeader, etc1_uint32 width, etc1_uint32 height); + +// Check if a PKM header is correctly formatted. + +etc1_bool etc1_pkm_is_valid(const etc1_byte* pHeader); + +// Read the image width from a PKM header + +etc1_uint32 etc1_pkm_get_width(const etc1_byte* pHeader); + +// Read the image height from a PKM header + +etc1_uint32 etc1_pkm_get_height(const etc1_byte* pHeader); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/lib/soil/image_DXT.c b/lib/SOIL2/image_DXT.c similarity index 100% rename from lib/soil/image_DXT.c rename to lib/SOIL2/image_DXT.c diff --git a/lib/soil/image_DXT.h b/lib/SOIL2/image_DXT.h similarity index 100% rename from lib/soil/image_DXT.h rename to lib/SOIL2/image_DXT.h diff --git a/lib/soil/image_helper.c b/lib/SOIL2/image_helper.c similarity index 95% rename from lib/soil/image_helper.c rename to lib/SOIL2/image_helper.c index 8d3c7440c..12c701ee6 100644 --- a/lib/soil/image_helper.c +++ b/lib/SOIL2/image_helper.c @@ -6,11 +6,6 @@ MIT license */ -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable: 4244) -#endif - #include "image_helper.h" #include #include @@ -327,7 +322,7 @@ find_max_RGBE for( i = width * height; i > 0; --i ) { /* float scale = powf( 2.0f, img[3] - 128.0f ) / 255.0f; */ - float scale = ldexp( 1.0f / 255.0f, (int)(img[3]) - 128 ); + float scale = (float)ldexp( 1.0f / 255.0f, (int)(img[3]) - 128 ); for( j = 0; j < 3; ++j ) { if( img[j] * scale > max_val ) @@ -368,14 +363,14 @@ RGBE_to_RGBdivA /* decode this pixel, and find the max */ float r,g,b,e, m; /* e = scale * powf( 2.0f, img[3] - 128.0f ) / 255.0f; */ - e = scale * ldexp( 1.0f / 255.0f, (int)(img[3]) - 128 ); + e = scale * (float)ldexp( 1.0f / 255.0f, (int)(img[3]) - 128 ); r = e * img[0]; g = e * img[1]; b = e * img[2]; m = (r > g) ? r : g; m = (b > m) ? b : m; /* and encode it into RGBdivA */ - iv = (m != 0.0f) ? (int)(255.0f / m) : 1.0f; + iv = (m != 0.0f) ? (int)(255.0f / m) : 1; iv = (iv < 1) ? 1 : iv; img[3] = (iv > 255) ? 255 : iv; iv = (int)(img[3] * r + 0.5f); @@ -417,14 +412,14 @@ RGBE_to_RGBdivA2 /* decode this pixel, and find the max */ float r,g,b,e, m; /* e = scale * powf( 2.0f, img[3] - 128.0f ) / 255.0f; */ - e = scale * ldexp( 1.0f / 255.0f, (int)(img[3]) - 128 ); + e = scale * (float)ldexp( 1.0f / 255.0f, (int)(img[3]) - 128 ); r = e * img[0]; g = e * img[1]; b = e * img[2]; m = (r > g) ? r : g; m = (b > m) ? b : m; /* and encode it into RGBdivA */ - iv = (m != 0.0f) ? (int)sqrtf( 255.0f * 255.0f / m ) : 1.0f; + iv = (m != 0.0f) ? (int)sqrtf( 255.0f * 255.0f / m ) : 1; iv = (iv < 1) ? 1 : iv; img[3] = (iv > 255) ? 255 : iv; iv = (int)(img[3] * img[3] * r / 255.0f + 0.5f); @@ -438,7 +433,3 @@ RGBE_to_RGBdivA2 } return 1; } - -#ifdef _MSC_VER -#pragma warning(pop) -#endif diff --git a/lib/soil/image_helper.h b/lib/SOIL2/image_helper.h similarity index 100% rename from lib/soil/image_helper.h rename to lib/SOIL2/image_helper.h diff --git a/lib/SOIL2/jo_jpeg.h b/lib/SOIL2/jo_jpeg.h new file mode 100644 index 000000000..353300b03 --- /dev/null +++ b/lib/SOIL2/jo_jpeg.h @@ -0,0 +1,340 @@ +/* public domain Simple, Minimalistic JPEG writer - http://jonolick.com + * + * Quick Notes: + * Based on a javascript jpeg writer + * JPEG baseline (no JPEG progressive) + * Supports 1, 3 or 4 component input. (luminance, RGB or RGBX) + * + * Latest revisions: + * 1.53 (2016-07-08) Added support to compile as plain C code. + * 1.52 (2012-22-11) Added support for specifying Luminance, RGB, or RGBA via comp(onents) argument (1, 3 and 4 respectively). + * 1.51 (2012-19-11) Fixed some warnings + * 1.50 (2012-18-11) MT safe. Simplified. Optimized. Reduced memory requirements. Zero allocations. No namespace polution. Approx 340 lines code. + * 1.10 (2012-16-11) compile fixes, added docs, + * changed from .h to .cpp (simpler to bootstrap), etc + * 1.00 (2012-02-02) initial release + * + * Basic usage: + * char *foo = new char[128*128*4]; // 4 component. RGBX format, where X is unused + * jo_write_jpg("foo.jpg", foo, 128, 128, 4, 90); // comp can be 1, 3, or 4. Lum, RGB, or RGBX respectively. + * + * */ + +#ifndef JO_INCLUDE_JPEG_H +#define JO_INCLUDE_JPEG_H + +// To get a header file for this, either cut and paste the header, +// or create jo_jpeg.h, #define JO_JPEG_HEADER_FILE_ONLY, and +// then include jo_jpeg.c from it. + +// Returns false on failure +extern int jo_write_jpg(const char *filename, const void *data, int width, int height, int comp, int quality); + +#endif // JO_INCLUDE_JPEG_H + +#ifndef JO_JPEG_HEADER_FILE_ONLY + +#if defined(_MSC_VER) && _MSC_VER >= 0x1400 +#define _CRT_SECURE_NO_WARNINGS // suppress warnings about fopen() +#endif + +#include +#include +#include + +static const unsigned char s_jo_ZigZag[] = { 0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18,24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63 }; + +static void jo_writeBits(FILE *fp, int *bitBuf, int *bitCnt, const unsigned short *bs) { + *bitCnt += bs[1]; + *bitBuf |= bs[0] << (24 - *bitCnt); + while(*bitCnt >= 8) { + unsigned char c = (*bitBuf >> 16) & 255; + putc(c, fp); + if(c == 255) { + putc(0, fp); + } + *bitBuf <<= 8; + *bitCnt -= 8; + } +} + +static void jo_DCT(float *d0, float *d1, float *d2, float *d3, float *d4, float *d5, float *d6, float *d7) { + float tmp0 = *d0 + *d7; + float tmp7 = *d0 - *d7; + float tmp1 = *d1 + *d6; + float tmp6 = *d1 - *d6; + float tmp2 = *d2 + *d5; + float tmp5 = *d2 - *d5; + float tmp3 = *d3 + *d4; + float tmp4 = *d3 - *d4; + + // Even part + float tmp10 = tmp0 + tmp3; // phase 2 + float tmp13 = tmp0 - tmp3; + float tmp11 = tmp1 + tmp2; + float tmp12 = tmp1 - tmp2; + + *d0 = tmp10 + tmp11; // phase 3 + *d4 = tmp10 - tmp11; + + float z1 = (tmp12 + tmp13) * 0.707106781f; // c4 + *d2 = tmp13 + z1; // phase 5 + *d6 = tmp13 - z1; + + // Odd part + tmp10 = tmp4 + tmp5; // phase 2 + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + // The rotator is modified from fig 4-8 to avoid extra negations. + float z5 = (tmp10 - tmp12) * 0.382683433f; // c6 + float z2 = tmp10 * 0.541196100f + z5; // c2-c6 + float z4 = tmp12 * 1.306562965f + z5; // c2+c6 + float z3 = tmp11 * 0.707106781f; // c4 + + float z11 = tmp7 + z3; // phase 5 + float z13 = tmp7 - z3; + + *d5 = z13 + z2; // phase 6 + *d3 = z13 - z2; + *d1 = z11 + z4; + *d7 = z11 - z4; +} + +static void jo_calcBits(int val, unsigned short bits[2]) { + int tmp1 = val < 0 ? -val : val; + val = val < 0 ? val-1 : val; + bits[1] = 1; + while(tmp1 >>= 1) { + ++bits[1]; + } + bits[0] = val & ((1<0)&&(DU[end0pos]==0); --end0pos) { + } + // end0pos = first element in reverse order !=0 + if(end0pos == 0) { + jo_writeBits(fp, bitBuf, bitCnt, EOB); + return DU[0]; + } + for(i = 1; i <= end0pos; ++i) { + int startpos = i; + for (; DU[i]==0 && i<=end0pos; ++i) { + } + int nrzeroes = i-startpos; + if ( nrzeroes >= 16 ) { + int lng = nrzeroes>>4; + for (nrmarker=1; nrmarker <= lng; ++nrmarker) + jo_writeBits(fp, bitBuf, bitCnt, M16zeroes); + nrzeroes &= 15; + } + unsigned short bits[2]; + jo_calcBits(DU[i], bits); + jo_writeBits(fp, bitBuf, bitCnt, HTAC[(nrzeroes<<4)+bits[1]]); + jo_writeBits(fp, bitBuf, bitCnt, bits); + } + if(end0pos != 63) { + jo_writeBits(fp, bitBuf, bitCnt, EOB); + } + return DU[0]; +} + +int jo_write_jpg(const char *filename, const void *data, int width, int height, int comp, int quality) { + // Constants that don't pollute global namespace + static const unsigned char std_dc_luminance_nrcodes[] = {0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0}; + static const unsigned char std_dc_luminance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; + static const unsigned char std_ac_luminance_nrcodes[] = {0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d}; + static const unsigned char std_ac_luminance_values[] = { + 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, + 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, + 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, + 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, + 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, + 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, + 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa + }; + static const unsigned char std_dc_chrominance_nrcodes[] = {0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0}; + static const unsigned char std_dc_chrominance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; + static const unsigned char std_ac_chrominance_nrcodes[] = {0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77}; + static const unsigned char std_ac_chrominance_values[] = { + 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, + 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, + 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, + 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, + 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, + 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa + }; + // Huffman tables + static const unsigned short YDC_HT[256][2] = { {0,2},{2,3},{3,3},{4,3},{5,3},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9}}; + static const unsigned short UVDC_HT[256][2] = { {0,2},{1,2},{2,2},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9},{1022,10},{2046,11}}; + static const unsigned short YAC_HT[256][2] = { + {10,4},{0,2},{1,2},{4,3},{11,4},{26,5},{120,7},{248,8},{1014,10},{65410,16},{65411,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {12,4},{27,5},{121,7},{502,9},{2038,11},{65412,16},{65413,16},{65414,16},{65415,16},{65416,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {28,5},{249,8},{1015,10},{4084,12},{65417,16},{65418,16},{65419,16},{65420,16},{65421,16},{65422,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {58,6},{503,9},{4085,12},{65423,16},{65424,16},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {59,6},{1016,10},{65430,16},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {122,7},{2039,11},{65438,16},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {123,7},{4086,12},{65446,16},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {250,8},{4087,12},{65454,16},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {504,9},{32704,15},{65462,16},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {505,9},{65470,16},{65471,16},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {506,9},{65479,16},{65480,16},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1017,10},{65488,16},{65489,16},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1018,10},{65497,16},{65498,16},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2040,11},{65506,16},{65507,16},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {65515,16},{65516,16},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2041,11},{65525,16},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} + }; + static const unsigned short UVAC_HT[256][2] = { + {0,2},{1,2},{4,3},{10,4},{24,5},{25,5},{56,6},{120,7},{500,9},{1014,10},{4084,12},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {11,4},{57,6},{246,8},{501,9},{2038,11},{4085,12},{65416,16},{65417,16},{65418,16},{65419,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {26,5},{247,8},{1015,10},{4086,12},{32706,15},{65420,16},{65421,16},{65422,16},{65423,16},{65424,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {27,5},{248,8},{1016,10},{4087,12},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{65430,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {58,6},{502,9},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{65438,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {59,6},{1017,10},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{65446,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {121,7},{2039,11},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{65454,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {122,7},{2040,11},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{65462,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {249,8},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{65470,16},{65471,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {503,9},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{65479,16},{65480,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {504,9},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{65488,16},{65489,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {505,9},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{65497,16},{65498,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {506,9},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{65506,16},{65507,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2041,11},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{65515,16},{65516,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {16352,14},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{65525,16},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1018,10},{32707,15},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} + }; + static const int YQT[] = {16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22,37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99}; + static const int UVQT[] = {17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99}; + static const float aasf[] = { 1.0f * 2.828427125f, 1.387039845f * 2.828427125f, 1.306562965f * 2.828427125f, 1.175875602f * 2.828427125f, 1.0f * 2.828427125f, 0.785694958f * 2.828427125f, 0.541196100f * 2.828427125f, 0.275899379f * 2.828427125f }; + int i, row, col, x, y, k, pos; + + if(!data || !filename || !width || !height || comp > 4 || comp < 1 || comp == 2) { + return 0; + } + + FILE *fp = fopen(filename, "wb"); + if(!fp) { + return 0; + } + + quality = quality ? quality : 90; + quality = quality < 1 ? 1 : quality > 100 ? 100 : quality; + quality = quality < 50 ? 5000 / quality : 200 - quality * 2; + + unsigned char YTable[64], UVTable[64]; + for(i = 0; i < 64; ++i) { + int yti = (YQT[i]*quality+50)/100; + YTable[s_jo_ZigZag[i]] = yti < 1 ? 1 : yti > 255 ? 255 : yti; + int uvti = (UVQT[i]*quality+50)/100; + UVTable[s_jo_ZigZag[i]] = uvti < 1 ? 1 : uvti > 255 ? 255 : uvti; + } + + float fdtbl_Y[64], fdtbl_UV[64]; + for(row = 0, k = 0; row < 8; ++row) { + for(col = 0; col < 8; ++col, ++k) { + fdtbl_Y[k] = 1 / (YTable [s_jo_ZigZag[k]] * aasf[row] * aasf[col]); + fdtbl_UV[k] = 1 / (UVTable[s_jo_ZigZag[k]] * aasf[row] * aasf[col]); + } + } + + // Write Headers + static const unsigned char head0[] = { 0xFF,0xD8,0xFF,0xE0,0,0x10,'J','F','I','F',0,1,1,0,0,1,0,1,0,0,0xFF,0xDB,0,0x84,0 }; + fwrite(head0, sizeof(head0), 1, fp); + fwrite(YTable, sizeof(YTable), 1, fp); + putc(1, fp); + fwrite(UVTable, sizeof(UVTable), 1, fp); + const unsigned char head1[] = { 0xFF,0xC0,0,0x11,8,(unsigned char)(height>>8),(unsigned char)(height&0xFF),(unsigned char)(width>>8),(unsigned char)(width&0xFF),3,1,0x11,0,2,0x11,1,3,0x11,1,0xFF,0xC4,0x01,0xA2,0 }; + fwrite(head1, sizeof(head1), 1, fp); + fwrite(std_dc_luminance_nrcodes+1, sizeof(std_dc_luminance_nrcodes)-1, 1, fp); + fwrite(std_dc_luminance_values, sizeof(std_dc_luminance_values), 1, fp); + putc(0x10, fp); // HTYACinfo + fwrite(std_ac_luminance_nrcodes+1, sizeof(std_ac_luminance_nrcodes)-1, 1, fp); + fwrite(std_ac_luminance_values, sizeof(std_ac_luminance_values), 1, fp); + putc(1, fp); // HTUDCinfo + fwrite(std_dc_chrominance_nrcodes+1, sizeof(std_dc_chrominance_nrcodes)-1, 1, fp); + fwrite(std_dc_chrominance_values, sizeof(std_dc_chrominance_values), 1, fp); + putc(0x11, fp); // HTUACinfo + fwrite(std_ac_chrominance_nrcodes+1, sizeof(std_ac_chrominance_nrcodes)-1, 1, fp); + fwrite(std_ac_chrominance_values, sizeof(std_ac_chrominance_values), 1, fp); + static const unsigned char head2[] = { 0xFF,0xDA,0,0xC,3,1,0,2,0x11,3,0x11,0,0x3F,0 }; + fwrite(head2, sizeof(head2), 1, fp); + + // Encode 8x8 macroblocks + const unsigned char *imageData = (const unsigned char *)data; + int DCY=0, DCU=0, DCV=0; + int bitBuf=0, bitCnt=0; + int ofsG = comp > 1 ? 1 : 0, ofsB = comp > 1 ? 2 : 0; + for(y = 0; y < height; y += 8) { + for(x = 0; x < width; x += 8) { + float YDU[64], UDU[64], VDU[64]; + for(row = y, pos = 0; row < y+8; ++row) { + for(col = x; col < x+8; ++col, ++pos) { + int p = row*width*comp + col*comp; + if(row >= height) { + p -= width*comp*(row+1 - height); + } + if(col >= width) { + p -= comp*(col+1 - width); + } + + float r = imageData[p+0], g = imageData[p+ofsG], b = imageData[p+ofsB]; + YDU[pos]=+0.29900f*r+0.58700f*g+0.11400f*b-128; + UDU[pos]=-0.16874f*r-0.33126f*g+0.50000f*b; + VDU[pos]=+0.50000f*r-0.41869f*g-0.08131f*b; + } + } + + DCY = jo_processDU(fp, &bitBuf, &bitCnt, YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCU = jo_processDU(fp, &bitBuf, &bitCnt, UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = jo_processDU(fp, &bitBuf, &bitCnt, VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + } + } + + // Do the bit alignment of the EOI marker + static const unsigned short fillBits[] = {0x7F, 7}; + jo_writeBits(fp, &bitBuf, &bitCnt, fillBits); + + // EOI + putc(0xFF, fp); + putc(0xD9, fp); + + fclose(fp); + return 1; +} + +#endif + diff --git a/lib/SOIL2/pkm_helper.h b/lib/SOIL2/pkm_helper.h new file mode 100644 index 000000000..0143e3f37 --- /dev/null +++ b/lib/SOIL2/pkm_helper.h @@ -0,0 +1,19 @@ +#ifndef PKM_HELPER_H +#define PKM_HELPER_H + +typedef struct { + char aName[6]; + unsigned short iBlank; + unsigned char iPaddedWidthMSB; + unsigned char iPaddedWidthLSB; + unsigned char iPaddedHeightMSB; + unsigned char iPaddedHeightLSB; + unsigned char iWidthMSB; + unsigned char iWidthLSB; + unsigned char iHeightMSB; + unsigned char iHeightLSB; +} PKMHeader; + +#define PKM_HEADER_SIZE 16 + +#endif diff --git a/lib/SOIL2/pvr_helper.h b/lib/SOIL2/pvr_helper.h new file mode 100644 index 000000000..bace1f38f --- /dev/null +++ b/lib/SOIL2/pvr_helper.h @@ -0,0 +1,264 @@ +#ifndef PVR_HELPER_H +#define PVR_HELPER_H + +// Taken from PowerVR SDK + +/*!*************************************************************************** + Describes the header of a PVR header-texture + *****************************************************************************/ +typedef struct +{ + unsigned int dwHeaderSize; /*!< size of the structure */ + unsigned int dwHeight; /*!< height of surface to be created */ + unsigned int dwWidth; /*!< width of input surface */ + unsigned int dwMipMapCount; /*!< number of mip-map levels requested */ + unsigned int dwpfFlags; /*!< pixel format flags */ + unsigned int dwTextureDataSize; /*!< Total size in bytes */ + unsigned int dwBitCount; /*!< number of bits per pixel */ + unsigned int dwRBitMask; /*!< mask for red bit */ + unsigned int dwGBitMask; /*!< mask for green bits */ + unsigned int dwBBitMask; /*!< mask for blue bits */ + unsigned int dwAlphaBitMask; /*!< mask for alpha channel */ + unsigned int dwPVR; /*!< magic number identifying pvr file */ + unsigned int dwNumSurfs; /*!< the number of surfaces present in the pvr */ +} PVR_Texture_Header; + +/***************************************************************************** + * ENUMS + *****************************************************************************/ + +enum PixelType +{ + MGLPT_ARGB_4444 = 0x00, + MGLPT_ARGB_1555, + MGLPT_RGB_565, + MGLPT_RGB_555, + MGLPT_RGB_888, + MGLPT_ARGB_8888, + MGLPT_ARGB_8332, + MGLPT_I_8, + MGLPT_AI_88, + MGLPT_1_BPP, + MGLPT_VY1UY0, + MGLPT_Y1VY0U, + MGLPT_PVRTC2, + MGLPT_PVRTC4, + MGLPT_PVRTC2_2, + MGLPT_PVRTC2_4, + + OGL_RGBA_4444= 0x10, + OGL_RGBA_5551, + OGL_RGBA_8888, + OGL_RGB_565, + OGL_RGB_555, + OGL_RGB_888, + OGL_I_8, + OGL_AI_88, + OGL_PVRTC2, + OGL_PVRTC4, + + // OGL_BGRA_8888 extension + OGL_BGRA_8888, + + D3D_DXT1 = 0x20, + D3D_DXT2, + D3D_DXT3, + D3D_DXT4, + D3D_DXT5, + + D3D_RGB_332, + D3D_AI_44, + D3D_LVU_655, + D3D_XLVU_8888, + D3D_QWVU_8888, + + //10 bits per channel + D3D_ABGR_2101010, + D3D_ARGB_2101010, + D3D_AWVU_2101010, + + //16 bits per channel + D3D_GR_1616, + D3D_VU_1616, + D3D_ABGR_16161616, + + //HDR formats + D3D_R16F, + D3D_GR_1616F, + D3D_ABGR_16161616F, + + //32 bits per channel + D3D_R32F, + D3D_GR_3232F, + D3D_ABGR_32323232F, + + // Ericsson + ETC_RGB_4BPP, + ETC_RGBA_EXPLICIT, + ETC_RGBA_INTERPOLATED, + + // DX10 + + + ePT_DX10_R32G32B32A32_FLOAT= 0x50, + ePT_DX10_R32G32B32A32_UINT , + ePT_DX10_R32G32B32A32_SINT, + + ePT_DX10_R32G32B32_FLOAT, + ePT_DX10_R32G32B32_UINT, + ePT_DX10_R32G32B32_SINT, + + ePT_DX10_R16G16B16A16_FLOAT , + ePT_DX10_R16G16B16A16_UNORM, + ePT_DX10_R16G16B16A16_UINT , + ePT_DX10_R16G16B16A16_SNORM , + ePT_DX10_R16G16B16A16_SINT , + + ePT_DX10_R32G32_FLOAT , + ePT_DX10_R32G32_UINT , + ePT_DX10_R32G32_SINT , + + ePT_DX10_R10G10B10A2_UNORM , + ePT_DX10_R10G10B10A2_UINT , + + ePT_DX10_R11G11B10_FLOAT , + + ePT_DX10_R8G8B8A8_UNORM , + ePT_DX10_R8G8B8A8_UNORM_SRGB , + ePT_DX10_R8G8B8A8_UINT , + ePT_DX10_R8G8B8A8_SNORM , + ePT_DX10_R8G8B8A8_SINT , + + ePT_DX10_R16G16_FLOAT , + ePT_DX10_R16G16_UNORM , + ePT_DX10_R16G16_UINT , + ePT_DX10_R16G16_SNORM , + ePT_DX10_R16G16_SINT , + + ePT_DX10_R32_FLOAT , + ePT_DX10_R32_UINT , + ePT_DX10_R32_SINT , + + ePT_DX10_R8G8_UNORM , + ePT_DX10_R8G8_UINT , + ePT_DX10_R8G8_SNORM , + ePT_DX10_R8G8_SINT , + + ePT_DX10_R16_FLOAT , + ePT_DX10_R16_UNORM , + ePT_DX10_R16_UINT , + ePT_DX10_R16_SNORM , + ePT_DX10_R16_SINT , + + ePT_DX10_R8_UNORM, + ePT_DX10_R8_UINT, + ePT_DX10_R8_SNORM, + ePT_DX10_R8_SINT, + + ePT_DX10_A8_UNORM, + ePT_DX10_R1_UNORM, + ePT_DX10_R9G9B9E5_SHAREDEXP, + ePT_DX10_R8G8_B8G8_UNORM, + ePT_DX10_G8R8_G8B8_UNORM, + + ePT_DX10_BC1_UNORM, + ePT_DX10_BC1_UNORM_SRGB, + + ePT_DX10_BC2_UNORM, + ePT_DX10_BC2_UNORM_SRGB, + + ePT_DX10_BC3_UNORM, + ePT_DX10_BC3_UNORM_SRGB, + + ePT_DX10_BC4_UNORM, + ePT_DX10_BC4_SNORM, + + ePT_DX10_BC5_UNORM, + ePT_DX10_BC5_SNORM, + + //ePT_DX10_B5G6R5_UNORM, // defined but obsolete - won't actually load in DX10 + //ePT_DX10_B5G5R5A1_UNORM, + //ePT_DX10_B8G8R8A8_UNORM, + //ePT_DX10_B8G8R8X8_UNORM, + + // OpenVG + + /* RGB{A,X} channel ordering */ + ePT_VG_sRGBX_8888 = 0x90, + ePT_VG_sRGBA_8888, + ePT_VG_sRGBA_8888_PRE, + ePT_VG_sRGB_565, + ePT_VG_sRGBA_5551, + ePT_VG_sRGBA_4444, + ePT_VG_sL_8, + ePT_VG_lRGBX_8888, + ePT_VG_lRGBA_8888, + ePT_VG_lRGBA_8888_PRE, + ePT_VG_lL_8, + ePT_VG_A_8, + ePT_VG_BW_1, + + /* {A,X}RGB channel ordering */ + ePT_VG_sXRGB_8888, + ePT_VG_sARGB_8888, + ePT_VG_sARGB_8888_PRE, + ePT_VG_sARGB_1555, + ePT_VG_sARGB_4444, + ePT_VG_lXRGB_8888, + ePT_VG_lARGB_8888, + ePT_VG_lARGB_8888_PRE, + + /* BGR{A,X} channel ordering */ + ePT_VG_sBGRX_8888, + ePT_VG_sBGRA_8888, + ePT_VG_sBGRA_8888_PRE, + ePT_VG_sBGR_565, + ePT_VG_sBGRA_5551, + ePT_VG_sBGRA_4444, + ePT_VG_lBGRX_8888, + ePT_VG_lBGRA_8888, + ePT_VG_lBGRA_8888_PRE, + + /* {A,X}BGR channel ordering */ + ePT_VG_sXBGR_8888, + ePT_VG_sABGR_8888 , + ePT_VG_sABGR_8888_PRE, + ePT_VG_sABGR_1555, + ePT_VG_sABGR_4444, + ePT_VG_lXBGR_8888, + ePT_VG_lABGR_8888, + ePT_VG_lABGR_8888_PRE, + + // max cap for iterating + END_OF_PIXEL_TYPES, + + MGLPT_NOTYPE = 0xff + +}; + +/***************************************************************************** + * constants + *****************************************************************************/ + +#define PVRTEX_MIPMAP (1<<8) // has mip map levels +#define PVRTEX_TWIDDLE (1<<9) // is twiddled +#define PVRTEX_BUMPMAP (1<<10) // has normals encoded for a bump map +#define PVRTEX_TILING (1<<11) // is bordered for tiled pvr +#define PVRTEX_CUBEMAP (1<<12) // is a cubemap/skybox +#define PVRTEX_FALSEMIPCOL (1<<13) // +#define PVRTEX_VOLUME (1<<14) +#define PVRTEX_PIXELTYPE 0xff // pixel type is always in the last 16bits of the flags +#define PVRTEX_IDENTIFIER 0x21525650 // the pvr identifier is the characters 'P','V','R' + +#define PVRTEX_V1_HEADER_SIZE 44 // old header size was 44 for identification purposes + +#define PVRTC2_MIN_TEXWIDTH 16 +#define PVRTC2_MIN_TEXHEIGHT 8 +#define PVRTC4_MIN_TEXWIDTH 8 +#define PVRTC4_MIN_TEXHEIGHT 8 +#define ETC_MIN_TEXWIDTH 4 +#define ETC_MIN_TEXHEIGHT 4 +#define DXT_MIN_TEXWIDTH 4 +#define DXT_MIN_TEXHEIGHT 4 + +#endif diff --git a/lib/SOIL2/stb_image.h b/lib/SOIL2/stb_image.h new file mode 100644 index 000000000..fa446cbff --- /dev/null +++ b/lib/SOIL2/stb_image.h @@ -0,0 +1,7240 @@ +/* stb_image - v2.15 - public domain image loader - http://nothings.org/stb_image.h + no warranty implied; use at your own risk + + Do this: + #define STB_IMAGE_IMPLEMENTATION + before you include this file in *one* C or C++ file to create the implementation. + + // i.e. it should look like this: + #include ... + #include ... + #include ... + #define STB_IMAGE_IMPLEMENTATION + #include "stb_image.h" + + You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. + And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free + + + QUICK NOTES: + Primarily of interest to game developers and other people who can + avoid problematic images and only need the trivial interface + + JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) + PNG 1/2/4/8/16-bit-per-channel + + TGA (not sure what subset, if a subset) + BMP non-1bpp, non-RLE + PSD (composited view only, no extra channels, 8/16 bit-per-channel) + + GIF (*comp always reports as 4-channel) + HDR (radiance rgbE format) + PIC (Softimage PIC) + PNM (PPM and PGM binary only) + + Animated GIF still needs a proper API, but here's one way to do it: + http://gist.github.com/urraka/685d9a6340b26b830d49 + + - decode from memory or through FILE (define STBI_NO_STDIO to remove code) + - decode from arbitrary I/O callbacks + - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON) + + Full documentation under "DOCUMENTATION" below. + + +LICENSE + + See end of file for license information. + +RECENT REVISION HISTORY: + + 2.15 (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-12-04) experimental 16-bit API, only for PNG so far; fixes + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64 + RGB-format JPEG; remove white matting in PSD; + allocate large structures on the stack; + correct channel count for PNG & BMP + 2.10 (2016-01-22) avoid warning introduced in 2.09 + 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) partial animated GIF support + limited 16-bit PSD support + minor bugs, code cleanup, and compiler warnings + + See end of file for full revision history. + + + ============================ Contributors ========================= + + Image formats Extensions, features + Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info) + Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info) + Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG) + Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks) + Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG) + Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) + Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) + github:urraka (animated gif) Junggon Kim (PNM comments) + Daniel Gibson (16-bit TGA) + socks-the-fox (16-bit PNG) + Jeremy Sawicki (handle all ImageNet JPGs) + Optimizations & bugfixes + Fabian "ryg" Giesen + Arseny Kapoulkine + + Bug & warning fixes + Marc LeBlanc David Woo Guillaume George Martins Mozeiko + Christpher Lloyd Jerry Jansson Joseph Thomson Phil Jordan + Dave Moore Roy Eltham Hayaki Saito Nathan Reed + Won Chun Luke Graham Johan Duparc Nick Verigakis + the Horde3D community Thomas Ruf Ronny Chevalier Baldur Karlsson + Janez Zemva John Bartholomew Michal Cichon github:rlyeh + Jonathan Blow Ken Hamada Tero Hanninen github:romigrou + Laurent Gomila Cort Stratton Sergio Gonzalez github:svdijk + Aruelien Pocheville Thibault Reuille Cass Everitt github:snagar + Ryamond Barbiero Paul Du Bois Engin Manap github:Zelex + Michaelangel007@github Philipp Wiesemann Dale Weiler github:grim210 + Oriol Ferrer Mesia Josh Tobin Matthew Gregan github:sammyhw + Blazej Dariusz Roszkowski Gregory Mullen github:phprus + +*/ + +#ifndef STBI_INCLUDE_STB_IMAGE_H +#define STBI_INCLUDE_STB_IMAGE_H + +// DOCUMENTATION +// +// Limitations: +// - no 16-bit-per-channel PNG +// - no 12-bit-per-channel JPEG +// - no JPEGs with arithmetic coding +// - no 1-bit BMP +// - GIF always returns *comp=4 +// +// Basic usage (see HDR discussion below for HDR usage): +// int x,y,n; +// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); +// // ... process data if not NULL ... +// // ... x = width, y = height, n = # 8-bit components per pixel ... +// // ... replace '0' with '1'..'4' to force that many components per pixel +// // ... but 'n' will always be the number that it would have been if you said 0 +// stbi_image_free(data) +// +// Standard parameters: +// int *x -- outputs image width in pixels +// int *y -- outputs image height in pixels +// int *channels_in_file -- outputs # of image components in image file +// int desired_channels -- if non-zero, # of image components requested in result +// +// The return value from an image loader is an 'unsigned char *' which points +// to the pixel data, or NULL on an allocation failure or if the image is +// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, +// with each pixel consisting of N interleaved 8-bit components; the first +// pixel pointed to is top-left-most in the image. There is no padding between +// image scanlines or between pixels, regardless of format. The number of +// components N is 'req_comp' if req_comp is non-zero, or *comp otherwise. +// If req_comp is non-zero, *comp has the number of components that _would_ +// have been output otherwise. E.g. if you set req_comp to 4, you will always +// get RGBA output, but you can check *comp to see if it's trivially opaque +// because e.g. there were only 3 channels in the source image. +// +// An output image with N components has the following components interleaved +// in this order in each pixel: +// +// N=#comp components +// 1 grey +// 2 grey, alpha +// 3 red, green, blue +// 4 red, green, blue, alpha +// +// If image loading fails for any reason, the return value will be NULL, +// and *x, *y, *comp will be unchanged. The function stbi_failure_reason() +// can be queried for an extremely brief, end-user unfriendly explanation +// of why the load failed. Define STBI_NO_FAILURE_STRINGS to avoid +// compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly +// more user-friendly ones. +// +// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. +// +// =========================================================================== +// +// Philosophy +// +// stb libraries are designed with the following priorities: +// +// 1. easy to use +// 2. easy to maintain +// 3. good performance +// +// Sometimes I let "good performance" creep up in priority over "easy to maintain", +// and for best performance I may provide less-easy-to-use APIs that give higher +// performance, in addition to the easy to use ones. Nevertheless, it's important +// to keep in mind that from the standpoint of you, a client of this library, +// all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all. +// +// Some secondary priorities arise directly from the first two, some of which +// make more explicit reasons why performance can't be emphasized. +// +// - Portable ("ease of use") +// - Small source code footprint ("easy to maintain") +// - No dependencies ("ease of use") +// +// =========================================================================== +// +// I/O callbacks +// +// I/O callbacks allow you to read from arbitrary sources, like packaged +// files or some other source. Data read from callbacks are processed +// through a small internal buffer (currently 128 bytes) to try to reduce +// overhead. +// +// The three functions you must define are "read" (reads some bytes of data), +// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). +// +// =========================================================================== +// +// SIMD support +// +// The JPEG decoder will try to automatically use SIMD kernels on x86 when +// supported by the compiler. For ARM Neon support, you must explicitly +// request it. +// +// (The old do-it-yourself SIMD API is no longer supported in the current +// code.) +// +// On x86, SSE2 will automatically be used when available based on a run-time +// test; if not, the generic C versions are used as a fall-back. On ARM targets, +// the typical path is to have separate builds for NEON and non-NEON devices +// (at least this is true for iOS and Android). Therefore, the NEON support is +// toggled by a build flag: define STBI_NEON to get NEON loops. +// +// If for some reason you do not want to use any of SIMD code, or if +// you have issues compiling it, you can disable it entirely by +// defining STBI_NO_SIMD. +// +// =========================================================================== +// +// HDR image support (disable by defining STBI_NO_HDR) +// +// stb_image now supports loading HDR images in general, and currently +// the Radiance .HDR file format, although the support is provided +// generically. You can still load any file through the existing interface; +// if you attempt to load an HDR file, it will be automatically remapped to +// LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; +// both of these constants can be reconfigured through this interface: +// +// stbi_hdr_to_ldr_gamma(2.2f); +// stbi_hdr_to_ldr_scale(1.0f); +// +// (note, do not use _inverse_ constants; stbi_image will invert them +// appropriately). +// +// Additionally, there is a new, parallel interface for loading files as +// (linear) floats to preserve the full dynamic range: +// +// float *data = stbi_loadf(filename, &x, &y, &n, 0); +// +// If you load LDR images through this interface, those images will +// be promoted to floating point values, run through the inverse of +// constants corresponding to the above: +// +// stbi_ldr_to_hdr_scale(1.0f); +// stbi_ldr_to_hdr_gamma(2.2f); +// +// Finally, given a filename (or an open file or memory block--see header +// file for details) containing image data, you can query for the "most +// appropriate" interface to use (that is, whether the image is HDR or +// not), using: +// +// stbi_is_hdr(char *filename); +// +// =========================================================================== +// +// iPhone PNG support: +// +// By default we convert iphone-formatted PNGs back to RGB, even though +// they are internally encoded differently. You can disable this conversion +// by by calling stbi_convert_iphone_png_to_rgb(0), in which case +// you will always just get the native iphone "format" through (which +// is BGR stored in RGB). +// +// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per +// pixel to remove any premultiplied alpha *only* if the image file explicitly +// says there's premultiplied data (currently only happens in iPhone images, +// and only if iPhone convert-to-rgb processing is on). +// +// =========================================================================== +// +// ADDITIONAL CONFIGURATION +// +// - You can suppress implementation of any of the decoders to reduce +// your code footprint by #defining one or more of the following +// symbols before creating the implementation. +// +// STBI_NO_JPEG +// STBI_NO_PNG +// STBI_NO_BMP +// STBI_NO_PSD +// STBI_NO_TGA +// STBI_NO_GIF +// STBI_NO_HDR +// STBI_NO_PIC +// STBI_NO_PNM (.ppm and .pgm) +// +// - You can request *only* certain decoders and suppress all other ones +// (this will be more forward-compatible, as addition of new decoders +// doesn't require you to disable them explicitly): +// +// STBI_ONLY_JPEG +// STBI_ONLY_PNG +// STBI_ONLY_BMP +// STBI_ONLY_PSD +// STBI_ONLY_TGA +// STBI_ONLY_GIF +// STBI_ONLY_HDR +// STBI_ONLY_PIC +// STBI_ONLY_PNM (.ppm and .pgm) +// +// - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still +// want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB +// + + +#ifndef STBI_NO_STDIO +#include +#endif // STBI_NO_STDIO + +#define STBI_VERSION 1 + +enum +{ + STBI_default = 0, // only used for req_comp + + STBI_grey = 1, + STBI_grey_alpha = 2, + STBI_rgb = 3, + STBI_rgb_alpha = 4 +}; + +typedef unsigned char stbi_uc; +typedef unsigned short stbi_us; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef STB_IMAGE_STATIC +#define STBIDEF static +#else +#define STBIDEF extern +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// PRIMARY API - works on images of any type +// + +// +// load image by filename, open file, or memory buffer +// + +typedef struct +{ + int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read + void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative + int (*eof) (void *user); // returns nonzero if we are at end of file/data +} stbi_io_callbacks; + +//////////////////////////////////// +// +// 8-bits-per-channel interface +// + +STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +// for stbi_load_from_file, file pointer is left pointing immediately after image +#endif + +//////////////////////////////////// +// +// 16-bits-per-channel interface +// + +STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +#ifndef STBI_NO_STDIO +STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +#endif +// @TODO the other variants + +//////////////////////////////////// +// +// float-per-channel interface +// +#ifndef STBI_NO_LINEAR + STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + + #ifndef STBI_NO_STDIO + STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); + #endif +#endif + +#ifndef STBI_NO_HDR + STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); + STBIDEF void stbi_hdr_to_ldr_scale(float scale); +#endif // STBI_NO_HDR + +#ifndef STBI_NO_LINEAR + STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); + STBIDEF void stbi_ldr_to_hdr_scale(float scale); +#endif // STBI_NO_LINEAR + +// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename); +STBIDEF int stbi_is_hdr_from_file(FILE *f); +#endif // STBI_NO_STDIO + + +// get a VERY brief reason for failure +// NOT THREADSAFE +STBIDEF const char *stbi_failure_reason (void); + +// free the loaded image -- this is just free() +STBIDEF void stbi_image_free (void *retval_from_stbi_load); + +// get image dimensions & components without fully decoding +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); + +#endif + + + +// for image formats that explicitly notate that they have premultiplied alpha, +// we just return the colors as stored in the file. set this flag to force +// unpremultiplication. results are undefined if the unpremultiply overflow. +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); + +// indicate whether we should process iphone images back to canonical format, +// or just pass them through "as-is" +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); + +// flip the image vertically, so the first pixel in the output array is the bottom left +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); + +// ZLIB client - used by PNG, available for other purposes + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); +STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + +STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + +#ifndef STBI_NO_DDS +#include "stbi_DDS.h" +#endif + +#ifndef STBI_NO_PVR +#include "stbi_pvr.h" +#endif + +#ifndef STBI_NO_PKM +#include "stbi_pkm.h" +#endif + +#ifndef STBI_NO_EXT +#include "stbi_ext.h" +#endif + +#ifdef __cplusplus +} +#endif + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBI_INCLUDE_STB_IMAGE_H + +#ifdef STB_IMAGE_IMPLEMENTATION + +#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ + || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ + || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ + || defined(STBI_ONLY_ZLIB) + #ifndef STBI_ONLY_JPEG + #define STBI_NO_JPEG + #endif + #ifndef STBI_ONLY_PNG + #define STBI_NO_PNG + #endif + #ifndef STBI_ONLY_BMP + #define STBI_NO_BMP + #endif + #ifndef STBI_ONLY_PSD + #define STBI_NO_PSD + #endif + #ifndef STBI_ONLY_TGA + #define STBI_NO_TGA + #endif + #ifndef STBI_ONLY_GIF + #define STBI_NO_GIF + #endif + #ifndef STBI_ONLY_HDR + #define STBI_NO_HDR + #endif + #ifndef STBI_ONLY_PIC + #define STBI_NO_PIC + #endif + #ifndef STBI_ONLY_PNM + #define STBI_NO_PNM + #endif +#endif + +#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) +#define STBI_NO_ZLIB +#endif + + +#include +#include // ptrdiff_t on osx +#include +#include +#include + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +#include // ldexp +#endif + +#ifndef STBI_NO_STDIO +#include +#endif + +#ifndef STBI_ASSERT +#include +#define STBI_ASSERT(x) assert(x) +#endif + + +#ifndef _MSC_VER + #ifdef __cplusplus + #define stbi_inline inline + #else + #define stbi_inline + #endif +#else + #define stbi_inline __forceinline +#endif + + +#ifdef _MSC_VER +typedef unsigned short stbi__uint16; +typedef signed short stbi__int16; +typedef unsigned int stbi__uint32; +typedef signed int stbi__int32; +#else +#include +typedef uint16_t stbi__uint16; +typedef int16_t stbi__int16; +typedef uint32_t stbi__uint32; +typedef int32_t stbi__int32; +#endif + +// should produce compiler error if size is wrong +typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; + +#ifdef _MSC_VER +#define STBI_NOTUSED(v) (void)(v) +#else +#define STBI_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef _MSC_VER +#define STBI_HAS_LROTL +#endif + +#ifdef STBI_HAS_LROTL + #define stbi_lrot(x,y) _lrotl(x,y) +#else + #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (32 - (y)))) +#endif + +#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) +// ok +#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." +#endif + +#ifndef STBI_MALLOC +#define STBI_MALLOC(sz) malloc(sz) +#define STBI_REALLOC(p,newsz) realloc(p,newsz) +#define STBI_FREE(p) free(p) +#endif + +#ifndef STBI_REALLOC_SIZED +#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) +#endif + +// x86/x64 detection +#if defined(__x86_64__) || defined(_M_X64) +#define STBI__X64_TARGET +#elif defined(__i386) || defined(_M_IX86) +#define STBI__X86_TARGET +#endif + +#if defined(__GNUC__) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) +// NOTE: not clear do we actually need this for the 64-bit path? +// gcc doesn't support sse2 intrinsics unless you compile with -msse2, +// (but compiling with -msse2 allows the compiler to use SSE2 everywhere; +// this is just broken and gcc are jerks for not fixing it properly +// http://www.virtualdub.org/blog/pivot/entry.php?id=363 ) +#define STBI_NO_SIMD +#endif + +#if defined(__MINGW32__) && !defined(__x86_64__) && !defined(STBI_NO_SIMD) +#define STBI_MINGW_ENABLE_SSE2 +#define STBI_FORCE_STACK_ALIGN __attribute__((force_align_arg_pointer)) +#else +#define STBI_FORCE_STACK_ALIGN +#endif + +#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) +#define STBI_SSE2 +#include + +#ifdef _MSC_VER + +#if _MSC_VER >= 1400 // not VC6 +#include // __cpuid +static int stbi__cpuid3(void) +{ + int info[4]; + __cpuid(info,1); + return info[3]; +} +#else +static int stbi__cpuid3(void) +{ + int res; + __asm { + mov eax,1 + cpuid + mov res,edx + } + return res; +} +#endif + +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name + +static int stbi__sse2_available() +{ + int info3 = stbi__cpuid3(); + return ((info3 >> 26) & 1) != 0; +} +#else // assume GCC-style if not VC++ +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) + +static int stbi__sse2_available() +{ + // If we're even attempting to compile this on GCC/Clang, that means + // -msse2 is on, which means the compiler is allowed to use SSE2 + // instructions at will, and so are we. + return 1; +} +#endif +#endif + +// ARM NEON +#if defined(STBI_NO_SIMD) && defined(STBI_NEON) +#undef STBI_NEON +#endif + +#ifdef STBI_NEON +#include +// assume GCC or Clang on ARM targets +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) +#endif + +#ifndef STBI_SIMD_ALIGN +#define STBI_SIMD_ALIGN(type, name) type name +#endif + +/////////////////////////////////////////////// +// +// stbi__context struct and start_xxx functions + +// stbi__context structure is our basic context used by all images, so it +// contains all the IO context, plus some basic image information +typedef struct +{ + stbi__uint32 img_x, img_y; + int img_n, img_out_n; + + stbi_io_callbacks io; + void *io_user_data; + + int read_from_callbacks; + int buflen; + stbi_uc buffer_start[128]; + + stbi_uc *img_buffer, *img_buffer_end; + stbi_uc *img_buffer_original, *img_buffer_original_end; +} stbi__context; + + +static void stbi__refill_buffer(stbi__context *s); + +// initialize a memory-decode context +static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) +{ + s->io.read = NULL; + s->read_from_callbacks = 0; + s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; + s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; +} + +// initialize a callback-based context +static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) +{ + s->io = *c; + s->io_user_data = user; + s->buflen = sizeof(s->buffer_start); + s->read_from_callbacks = 1; + s->img_buffer_original = s->buffer_start; + stbi__refill_buffer(s); + s->img_buffer_original_end = s->img_buffer_end; +} + +#ifndef STBI_NO_STDIO + +static int stbi__stdio_read(void *user, char *data, int size) +{ + return (int) fread(data,1,size,(FILE*) user); +} + +static void stbi__stdio_skip(void *user, int n) +{ + fseek((FILE*) user, n, SEEK_CUR); +} + +static int stbi__stdio_eof(void *user) +{ + return feof((FILE*) user); +} + +static stbi_io_callbacks stbi__stdio_callbacks = +{ + stbi__stdio_read, + stbi__stdio_skip, + stbi__stdio_eof, +}; + +static void stbi__start_file(stbi__context *s, FILE *f) +{ + stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); +} + +//static void stop_file(stbi__context *s) { } + +#endif // !STBI_NO_STDIO + +static void stbi__rewind(stbi__context *s) +{ + // conceptually rewind SHOULD rewind to the beginning of the stream, + // but we just rewind to the beginning of the initial buffer, because + // we only use it after doing 'test', which only ever looks at at most 92 bytes + s->img_buffer = s->img_buffer_original; + s->img_buffer_end = s->img_buffer_original_end; +} + +enum +{ + STBI_ORDER_RGB, + STBI_ORDER_BGR +}; + +typedef struct +{ + int bits_per_channel; + int num_channels; + int channel_order; +} stbi__result_info; + +#ifndef STBI_NO_JPEG +static int stbi__jpeg_test(stbi__context *s); +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNG +static int stbi__png_test(stbi__context *s); +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_BMP +static int stbi__bmp_test(stbi__context *s); +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_TGA +static int stbi__tga_test(stbi__context *s); +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s); +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc); +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_HDR +static int stbi__hdr_test(stbi__context *s); +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_test(stbi__context *s); +static void *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_GIF +static int stbi__gif_test(stbi__context *s); +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNM +static int stbi__pnm_test(stbi__context *s); +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_DDS +static int stbi__dds_test(stbi__context *s); +static void *stbi__dds_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__dds_info(stbi__context *s, int *x, int *y, int *comp, int *iscompressed); +#endif + +#ifndef STBI_NO_PVR +static int stbi__pvr_test(stbi__context *s); +static void *stbi__pvr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__pvr_info(stbi__context *s, int *x, int *y, int *comp, int * iscompressed); +#endif + +#ifndef STBI_NO_PKM +static int stbi__pkm_test(stbi__context *s); +static void *stbi__pkm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__pkm_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +// this is not threadsafe +static const char *stbi__g_failure_reason; + +STBIDEF const char *stbi_failure_reason(void) +{ + return stbi__g_failure_reason; +} + +static int stbi__err(const char *str) +{ + stbi__g_failure_reason = str; + return 0; +} + +static void *stbi__malloc(size_t size) +{ + return STBI_MALLOC(size); +} + +// stb_image uses ints pervasively, including for offset calculations. +// therefore the largest decoded image size we can support with the +// current code, even on 64-bit targets, is INT_MAX. this is not a +// significant limitation for the intended use case. +// +// we do, however, need to make sure our size calculations don't +// overflow. hence a few helper functions for size calculations that +// multiply integers together, making sure that they're non-negative +// and no overflow occurs. + +// return 1 if the sum is valid, 0 on overflow. +// negative terms are considered invalid. +static int stbi__addsizes_valid(int a, int b) +{ + if (b < 0) return 0; + // now 0 <= b <= INT_MAX, hence also + // 0 <= INT_MAX - b <= INTMAX. + // And "a + b <= INT_MAX" (which might overflow) is the + // same as a <= INT_MAX - b (no overflow) + return a <= INT_MAX - b; +} + +// returns 1 if the product is valid, 0 on overflow. +// negative factors are considered invalid. +static int stbi__mul2sizes_valid(int a, int b) +{ + if (a < 0 || b < 0) return 0; + if (b == 0) return 1; // mul-by-0 is always safe + // portable way to check for no overflows in a*b + return a <= INT_MAX/b; +} + +// returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow +static int stbi__mad2sizes_valid(int a, int b, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add); +} + +// returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow +static int stbi__mad3sizes_valid(int a, int b, int c, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__addsizes_valid(a*b*c, add); +} + +// returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow +static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add); +} + +// mallocs with size overflow checking +static void *stbi__malloc_mad2(int a, int b, int add) +{ + if (!stbi__mad2sizes_valid(a, b, add)) return NULL; + return stbi__malloc(a*b + add); +} + +static void *stbi__malloc_mad3(int a, int b, int c, int add) +{ + if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL; + return stbi__malloc(a*b*c + add); +} + +static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) +{ + if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL; + return stbi__malloc(a*b*c*d + add); +} + +// stbi__err - error +// stbi__errpf - error returning pointer to float +// stbi__errpuc - error returning pointer to unsigned char + +#ifdef STBI_NO_FAILURE_STRINGS + #define stbi__err(x,y) 0 +#elif defined(STBI_FAILURE_USERMSG) + #define stbi__err(x,y) stbi__err(y) +#else + #define stbi__err(x,y) stbi__err(x) +#endif + +#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) +#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) + +STBIDEF void stbi_image_free(void *retval_from_stbi_load) +{ + STBI_FREE(retval_from_stbi_load); +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); +#endif + +#ifndef STBI_NO_HDR +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); +#endif + +static int stbi__vertically_flip_on_load = 0; + +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load = flag_true_if_should_flip; +} + +static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields + ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed + ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order + ri->num_channels = 0; + + #ifndef STBI_NO_JPEG + if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PNG + if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_BMP + if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_GIF + if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PSD + if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc); + #endif + #ifndef STBI_NO_PIC + if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PNM + if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_DDS + if (stbi__dds_test(s)) return stbi__dds_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_PVR + if (stbi__pvr_test(s)) return stbi__pvr_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_PKM + if (stbi__pkm_test(s)) return stbi__pkm_load(s,x,y,comp,req_comp); + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri); + return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); + } + #endif + + #ifndef STBI_NO_TGA + // test tga last because it's a crappy test! + if (stbi__tga_test(s)) + return stbi__tga_load(s,x,y,comp,req_comp, ri); + #endif + + return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); +} + +static stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi_uc *reduced; + + reduced = (stbi_uc *) stbi__malloc(img_len); + if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling + + STBI_FREE(orig); + return reduced; +} + +static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi__uint16 *enlarged; + + enlarged = (stbi__uint16 *) stbi__malloc(img_len*2); + if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff + + STBI_FREE(orig); + return enlarged; +} + +static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8); + + if (result == NULL) + return NULL; + + if (ri.bits_per_channel != 8) { + STBI_ASSERT(ri.bits_per_channel == 16); + result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 8; + } + + // @TODO: move stbi__convert_format to here + + if (stbi__vertically_flip_on_load) { + int w = *x, h = *y; + int channels = req_comp ? req_comp : *comp; + int row,col,z; + stbi_uc *image = (stbi_uc *) result; + + // @OPTIMIZE: use a bigger temp buffer and memcpy multiple pixels at once + for (row = 0; row < (h>>1); row++) { + for (col = 0; col < w; col++) { + for (z = 0; z < channels; z++) { + stbi_uc temp = image[(row * w + col) * channels + z]; + image[(row * w + col) * channels + z] = image[((h - row - 1) * w + col) * channels + z]; + image[((h - row - 1) * w + col) * channels + z] = temp; + } + } + } + } + + return (unsigned char *) result; +} + +static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16); + + if (result == NULL) + return NULL; + + if (ri.bits_per_channel != 16) { + STBI_ASSERT(ri.bits_per_channel == 8); + result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 16; + } + + // @TODO: move stbi__convert_format16 to here + // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision + + if (stbi__vertically_flip_on_load) { + int w = *x, h = *y; + int channels = req_comp ? req_comp : *comp; + int row,col,z; + stbi__uint16 *image = (stbi__uint16 *) result; + + // @OPTIMIZE: use a bigger temp buffer and memcpy multiple pixels at once + for (row = 0; row < (h>>1); row++) { + for (col = 0; col < w; col++) { + for (z = 0; z < channels; z++) { + stbi__uint16 temp = image[(row * w + col) * channels + z]; + image[(row * w + col) * channels + z] = image[((h - row - 1) * w + col) * channels + z]; + image[((h - row - 1) * w + col) * channels + z] = temp; + } + } + } + } + + return (stbi__uint16 *) result; +} + +#ifndef STBI_NO_HDR +static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) +{ + if (stbi__vertically_flip_on_load && result != NULL) { + int w = *x, h = *y; + int depth = req_comp ? req_comp : *comp; + int row,col,z; + float temp; + + // @OPTIMIZE: use a bigger temp buffer and memcpy multiple pixels at once + for (row = 0; row < (h>>1); row++) { + for (col = 0; col < w; col++) { + for (z = 0; z < depth; z++) { + temp = result[(row * w + col) * depth + z]; + result[(row * w + col) * depth + z] = result[((h - row - 1) * w + col) * depth + z]; + result[((h - row - 1) * w + col) * depth + z] = temp; + } + } + } + } +} +#endif + +#ifndef STBI_NO_STDIO + +static FILE *stbi__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + + +STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + unsigned char *result; + if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__uint16 *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + stbi__uint16 *result; + if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file_16(f,x,y,comp,req_comp); + fclose(f); + return result; +} + + +#endif //!STBI_NO_STDIO + +STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_LINEAR +static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + stbi__result_info ri; + float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri); + if (hdr_data) + stbi__float_postprocess(hdr_data,x,y,comp,req_comp); + return hdr_data; + } + #endif + data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp); + if (data) + return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); + return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); +} + +STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_STDIO +STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + float *result; + FILE *f = stbi__fopen(filename, "rb"); + if (!f) return stbi__errpf("can't fopen", "Unable to open file"); + result = stbi_loadf_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_file(&s,f); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} +#endif // !STBI_NO_STDIO + +#endif // !STBI_NO_LINEAR + +// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is +// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always +// reports false! + +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(buffer); + STBI_NOTUSED(len); + return 0; + #endif +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result=0; + if (f) { + result = stbi_is_hdr_from_file(f); + fclose(f); + } + return result; +} + +STBIDEF int stbi_is_hdr_from_file(FILE *f) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_file(&s,f); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(f); + return 0; + #endif +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(clbk); + STBI_NOTUSED(user); + return 0; + #endif +} + +#ifndef STBI_NO_LINEAR +static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; + +STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } +STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } +#endif + +static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; + +STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } +STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } + + +////////////////////////////////////////////////////////////////////////////// +// +// Common code used by all image loaders +// + +enum +{ + STBI__SCAN_load=0, + STBI__SCAN_type, + STBI__SCAN_header +}; + +static void stbi__refill_buffer(stbi__context *s) +{ + int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); + if (n == 0) { + // at end of file, treat same as if from memory, but need to handle case + // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file + s->read_from_callbacks = 0; + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start+1; + *s->img_buffer = 0; + } else { + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start + n; + } +} + +stbi_inline static stbi_uc stbi__get8(stbi__context *s) +{ + if (s->img_buffer < s->img_buffer_end) + return *s->img_buffer++; + if (s->read_from_callbacks) { + stbi__refill_buffer(s); + return *s->img_buffer++; + } + return 0; +} + +stbi_inline static int stbi__at_eof(stbi__context *s) +{ + if (s->io.read) { + if (!(s->io.eof)(s->io_user_data)) return 0; + // if feof() is true, check if buffer = end + // special case: we've only got the special 0 character at the end + if (s->read_from_callbacks == 0) return 1; + } + + return s->img_buffer >= s->img_buffer_end; +} + +static void stbi__skip(stbi__context *s, int n) +{ + if (n < 0) { + s->img_buffer = s->img_buffer_end; + return; + } + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + s->img_buffer = s->img_buffer_end; + (s->io.skip)(s->io_user_data, n - blen); + return; + } + } + s->img_buffer += n; +} + +static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) +{ + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + int res, count; + + memcpy(buffer, s->img_buffer, blen); + + count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); + res = (count == (n-blen)); + s->img_buffer = s->img_buffer_end; + return res; + } + } + + if (s->img_buffer+n <= s->img_buffer_end) { + memcpy(buffer, s->img_buffer, n); + s->img_buffer += n; + return 1; + } else + return 0; +} + +static int stbi__get16be(stbi__context *s) +{ + int z = stbi__get8(s); + return (z << 8) + stbi__get8(s); +} + +static stbi__uint32 stbi__get32be(stbi__context *s) +{ + stbi__uint32 z = stbi__get16be(s); + return (z << 16) + stbi__get16be(s); +} + +#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) +// nothing +#else +static int stbi__get16le(stbi__context *s) +{ + int z = stbi__get8(s); + return z + (stbi__get8(s) << 8); +} +#endif + +#ifndef STBI_NO_BMP +static stbi__uint32 stbi__get32le(stbi__context *s) +{ + stbi__uint32 z = stbi__get16le(s); + return z + (stbi__get16le(s) << 16); +} +#endif + +#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings + + +////////////////////////////////////////////////////////////////////////////// +// +// generic converter from built-in img_n to req_comp +// individual types do this automatically as much as possible (e.g. jpeg +// does all cases internally since it needs to colorspace convert anyway, +// and it never has alpha, so very few cases ). png can automatically +// interleave an alpha=255 channel, but falls back to this for other cases +// +// assume data buffer is malloced, so malloc a new one and free that one +// only failure mode is malloc failing + +static stbi_uc stbi__compute_y(int r, int g, int b) +{ + return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); +} + +static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + unsigned char *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0); + if (good == NULL) { + STBI_FREE(data); + return stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + unsigned char *src = data + j * x * img_n ; + unsigned char *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0], dest[1]=255; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=255; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=255; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = 255; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; } break; + default: STBI_ASSERT(0); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} + +static stbi__uint16 stbi__compute_y_16(int r, int g, int b) +{ + return (stbi__uint16) (((r*77) + (g*150) + (29*b)) >> 8); +} + +static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + stbi__uint16 *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2); + if (good == NULL) { + STBI_FREE(data); + return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + stbi__uint16 *src = data + j * x * img_n ; + stbi__uint16 *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0], dest[1]=0xffff; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=0xffff; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=0xffff; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]), dest[1] = 0xffff; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]), dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; } break; + default: STBI_ASSERT(0); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) +{ + int i,k,n; + float *output; + if (!data) return NULL; + output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); + } + if (k < comp) output[i*comp + k] = data[i*comp+k]/255.0f; + } + STBI_FREE(data); + return output; +} +#endif + +#ifndef STBI_NO_HDR +#define stbi__float2int(x) ((int) (x)) +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) +{ + int i,k,n; + stbi_uc *output; + if (!data) return NULL; + output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + if (k < comp) { + float z = data[i*comp+k] * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + } + STBI_FREE(data); + return output; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// "baseline" JPEG/JFIF decoder +// +// simple implementation +// - doesn't support delayed output of y-dimension +// - simple interface (only one output format: 8-bit interleaved RGB) +// - doesn't try to recover corrupt jpegs +// - doesn't allow partial loading, loading multiple at once +// - still fast on x86 (copying globals into locals doesn't help x86) +// - allocates lots of intermediate memory (full size of all components) +// - non-interleaved case requires this anyway +// - allows good upsampling (see next) +// high-quality +// - upsampled channels are bilinearly interpolated, even across blocks +// - quality integer IDCT derived from IJG's 'slow' +// performance +// - fast huffman; reasonable integer IDCT +// - some SIMD kernels for common paths on targets with SSE2/NEON +// - uses a lot of intermediate memory, could cache poorly + +#ifndef STBI_NO_JPEG + +// huffman decoding acceleration +#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache + +typedef struct +{ + stbi_uc fast[1 << FAST_BITS]; + // weirdly, repacking this into AoS is a 10% speed loss, instead of a win + stbi__uint16 code[256]; + stbi_uc values[256]; + stbi_uc size[257]; + unsigned int maxcode[18]; + int delta[17]; // old 'firstsymbol' - old 'firstcode' +} stbi__huffman; + +typedef struct +{ + stbi__context *s; + stbi__huffman huff_dc[4]; + stbi__huffman huff_ac[4]; + stbi__uint16 dequant[4][64]; + stbi__int16 fast_ac[4][1 << FAST_BITS]; + +// sizes for components, interleaved MCUs + int img_h_max, img_v_max; + int img_mcu_x, img_mcu_y; + int img_mcu_w, img_mcu_h; + +// definition of jpeg image component + struct + { + int id; + int h,v; + int tq; + int hd,ha; + int dc_pred; + + int x,y,w2,h2; + stbi_uc *data; + void *raw_data, *raw_coeff; + stbi_uc *linebuf; + short *coeff; // progressive only + int coeff_w, coeff_h; // number of 8x8 coefficient blocks + } img_comp[4]; + + stbi__uint32 code_buffer; // jpeg entropy-coded buffer + int code_bits; // number of valid bits + unsigned char marker; // marker seen while filling entropy buffer + int nomore; // flag if we saw a marker so must stop + + int progressive; + int spec_start; + int spec_end; + int succ_high; + int succ_low; + int eob_run; + int jfif; + int app14_color_transform; // Adobe APP14 tag + int rgb; + + int scan_n, order[4]; + int restart_interval, todo; + +// kernels + void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); + void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); + stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); +} stbi__jpeg; + +static int stbi__build_huffman(stbi__huffman *h, int *count) +{ + int i,j,k=0,code; + // build size list for each symbol (from JPEG spec) + for (i=0; i < 16; ++i) + for (j=0; j < count[i]; ++j) + h->size[k++] = (stbi_uc) (i+1); + h->size[k] = 0; + + // compute actual symbols (from jpeg spec) + code = 0; + k = 0; + for(j=1; j <= 16; ++j) { + // compute delta to add to code to compute symbol id + h->delta[j] = k - code; + if (h->size[k] == j) { + while (h->size[k] == j) + h->code[k++] = (stbi__uint16) (code++); + if (code-1 >= (1 << j)) return stbi__err("bad code lengths","Corrupt JPEG"); + } + // compute largest code + 1 for this size, preshifted as needed later + h->maxcode[j] = code << (16-j); + code <<= 1; + } + h->maxcode[j] = 0xffffffff; + + // build non-spec acceleration table; 255 is flag for not-accelerated + memset(h->fast, 255, 1 << FAST_BITS); + for (i=0; i < k; ++i) { + int s = h->size[i]; + if (s <= FAST_BITS) { + int c = h->code[i] << (FAST_BITS-s); + int m = 1 << (FAST_BITS-s); + for (j=0; j < m; ++j) { + h->fast[c+j] = (stbi_uc) i; + } + } + } + return 1; +} + +// build a table that decodes both magnitude and value of small ACs in +// one go. +static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) +{ + int i; + for (i=0; i < (1 << FAST_BITS); ++i) { + stbi_uc fast = h->fast[i]; + fast_ac[i] = 0; + if (fast < 255) { + int rs = h->values[fast]; + int run = (rs >> 4) & 15; + int magbits = rs & 15; + int len = h->size[fast]; + + if (magbits && len + magbits <= FAST_BITS) { + // magnitude code followed by receive_extend code + int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); + int m = 1 << (magbits - 1); + if (k < m) k += (~0U << magbits) + 1; + // if the result is small enough, we can fit it in fast_ac table + if (k >= -128 && k <= 127) + fast_ac[i] = (stbi__int16) ((k << 8) + (run << 4) + (len + magbits)); + } + } + } +} + +static void stbi__grow_buffer_unsafe(stbi__jpeg *j) +{ + do { + int b = j->nomore ? 0 : stbi__get8(j->s); + if (b == 0xff) { + int c = stbi__get8(j->s); + while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes + if (c != 0) { + j->marker = (unsigned char) c; + j->nomore = 1; + return; + } + } + j->code_buffer |= b << (24 - j->code_bits); + j->code_bits += 8; + } while (j->code_bits <= 24); +} + +// (1 << n) - 1 +static stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; + +// decode a jpeg huffman value from the bitstream +stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) +{ + unsigned int temp; + int c,k; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + // look at the top FAST_BITS and determine what symbol ID it is, + // if the code is <= FAST_BITS + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + k = h->fast[c]; + if (k < 255) { + int s = h->size[k]; + if (s > j->code_bits) + return -1; + j->code_buffer <<= s; + j->code_bits -= s; + return h->values[k]; + } + + // naive test is to shift the code_buffer down so k bits are + // valid, then test against maxcode. To speed this up, we've + // preshifted maxcode left so that it has (16-k) 0s at the + // end; in other words, regardless of the number of bits, it + // wants to be compared against something shifted to have 16; + // that way we don't need to shift inside the loop. + temp = j->code_buffer >> 16; + for (k=FAST_BITS+1 ; ; ++k) + if (temp < h->maxcode[k]) + break; + if (k == 17) { + // error! code not found + j->code_bits -= 16; + return -1; + } + + if (k > j->code_bits) + return -1; + + // convert the huffman code to the symbol id + c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; + STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); + + // convert the id to a symbol + j->code_bits -= k; + j->code_buffer <<= k; + return h->values[c]; +} + +// bias[n] = (-1<code_bits < n) stbi__grow_buffer_unsafe(j); + + sgn = (stbi__int32)j->code_buffer >> 31; // sign bit is always in MSB + k = stbi_lrot(j->code_buffer, n); + STBI_ASSERT(n >= 0 && n < (int) (sizeof(stbi__bmask)/sizeof(*stbi__bmask))); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k + (stbi__jbias[n] & ~sgn); +} + +// get some unsigned bits +stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) +{ + unsigned int k; + if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k; +} + +stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) +{ + unsigned int k; + if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); + k = j->code_buffer; + j->code_buffer <<= 1; + --j->code_bits; + return k & 0x80000000; +} + +// given a value that's at position X in the zigzag stream, +// where does it appear in the 8x8 matrix coded as row-major? +static stbi_uc stbi__jpeg_dezigzag[64+15] = +{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + // let corrupt input sample past end + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63 +}; + +// decode one 64-entry block-- +static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi__uint16 *dequant) +{ + int diff,dc,k; + int t; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + + // 0 all the ac values now so we can do it 32-bits at a time + memset(data,0,64*sizeof(data[0])); + + diff = t ? stbi__extend_receive(j, t) : 0; + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc * dequant[0]); + + // decode AC components, see JPEG spec + k = 1; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * dequant[zig]); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (rs != 0xf0) break; // end block + k += 16; + } else { + k += r; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); + } + } + } while (k < 64); + return 1; +} + +static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) +{ + int diff,dc; + int t; + if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + if (j->succ_high == 0) { + // first scan for DC coefficient, must be first + memset(data,0,64*sizeof(data[0])); // 0 all the ac values now + t = stbi__jpeg_huff_decode(j, hdc); + diff = t ? stbi__extend_receive(j, t) : 0; + + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc << j->succ_low); + } else { + // refinement scan for DC coefficient + if (stbi__jpeg_get_bit(j)) + data[0] += (short) (1 << j->succ_low); + } + return 1; +} + +// @OPTIMIZE: store non-zigzagged during the decode passes, +// and only de-zigzag when dequantizing +static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) +{ + int k; + if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->succ_high == 0) { + int shift = j->succ_low; + + if (j->eob_run) { + --j->eob_run; + return 1; + } + + k = j->spec_start; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) << shift); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r); + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + --j->eob_run; + break; + } + k += 16; + } else { + k += r; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) << shift); + } + } + } while (k <= j->spec_end); + } else { + // refinement scan for these AC coefficients + + short bit = (short) (1 << j->succ_low); + + if (j->eob_run) { + --j->eob_run; + for (k = j->spec_start; k <= j->spec_end; ++k) { + short *p = &data[stbi__jpeg_dezigzag[k]]; + if (*p != 0) + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } + } else { + k = j->spec_start; + do { + int r,s; + int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r) - 1; + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + r = 64; // force end of block + } else { + // r=15 s=0 should write 16 0s, so we just do + // a run of 15 0s and then write s (which is 0), + // so we don't have to do anything special here + } + } else { + if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); + // sign bit + if (stbi__jpeg_get_bit(j)) + s = bit; + else + s = -bit; + } + + // advance by r + while (k <= j->spec_end) { + short *p = &data[stbi__jpeg_dezigzag[k++]]; + if (*p != 0) { + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } else { + if (r == 0) { + *p = (short) s; + break; + } + --r; + } + } + } while (k <= j->spec_end); + } + } + return 1; +} + +// take a -128..127 value and stbi__clamp it and convert to 0..255 +stbi_inline static stbi_uc stbi__clamp(int x) +{ + // trick to use a single test to catch both cases + if ((unsigned int) x > 255) { + if (x < 0) return 0; + if (x > 255) return 255; + } + return (stbi_uc) x; +} + +#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) +#define stbi__fsh(x) ((x) << 12) + +// derived from jidctint -- DCT_ISLOW +#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ + int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ + p2 = s2; \ + p3 = s6; \ + p1 = (p2+p3) * stbi__f2f(0.5411961f); \ + t2 = p1 + p3*stbi__f2f(-1.847759065f); \ + t3 = p1 + p2*stbi__f2f( 0.765366865f); \ + p2 = s0; \ + p3 = s4; \ + t0 = stbi__fsh(p2+p3); \ + t1 = stbi__fsh(p2-p3); \ + x0 = t0+t3; \ + x3 = t0-t3; \ + x1 = t1+t2; \ + x2 = t1-t2; \ + t0 = s7; \ + t1 = s5; \ + t2 = s3; \ + t3 = s1; \ + p3 = t0+t2; \ + p4 = t1+t3; \ + p1 = t0+t3; \ + p2 = t1+t2; \ + p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ + t0 = t0*stbi__f2f( 0.298631336f); \ + t1 = t1*stbi__f2f( 2.053119869f); \ + t2 = t2*stbi__f2f( 3.072711026f); \ + t3 = t3*stbi__f2f( 1.501321110f); \ + p1 = p5 + p1*stbi__f2f(-0.899976223f); \ + p2 = p5 + p2*stbi__f2f(-2.562915447f); \ + p3 = p3*stbi__f2f(-1.961570560f); \ + p4 = p4*stbi__f2f(-0.390180644f); \ + t3 += p1+p4; \ + t2 += p2+p3; \ + t1 += p2+p4; \ + t0 += p1+p3; + +static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) +{ + int i,val[64],*v=val; + stbi_uc *o; + short *d = data; + + // columns + for (i=0; i < 8; ++i,++d, ++v) { + // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing + if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 + && d[40]==0 && d[48]==0 && d[56]==0) { + // no shortcut 0 seconds + // (1|2|3|4|5|6|7)==0 0 seconds + // all separate -0.047 seconds + // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds + int dcterm = d[0] << 2; + v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; + } else { + STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) + // constants scaled things up by 1<<12; let's bring them back + // down, but keep 2 extra bits of precision + x0 += 512; x1 += 512; x2 += 512; x3 += 512; + v[ 0] = (x0+t3) >> 10; + v[56] = (x0-t3) >> 10; + v[ 8] = (x1+t2) >> 10; + v[48] = (x1-t2) >> 10; + v[16] = (x2+t1) >> 10; + v[40] = (x2-t1) >> 10; + v[24] = (x3+t0) >> 10; + v[32] = (x3-t0) >> 10; + } + } + + for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { + // no fast case since the first 1D IDCT spread components out + STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) + // constants scaled things up by 1<<12, plus we had 1<<2 from first + // loop, plus horizontal and vertical each scale by sqrt(8) so together + // we've got an extra 1<<3, so 1<<17 total we need to remove. + // so we want to round that, which means adding 0.5 * 1<<17, + // aka 65536. Also, we'll end up with -128 to 127 that we want + // to encode as 0..255 by adding 128, so we'll add that before the shift + x0 += 65536 + (128<<17); + x1 += 65536 + (128<<17); + x2 += 65536 + (128<<17); + x3 += 65536 + (128<<17); + // tried computing the shifts into temps, or'ing the temps to see + // if any were out of range, but that was slower + o[0] = stbi__clamp((x0+t3) >> 17); + o[7] = stbi__clamp((x0-t3) >> 17); + o[1] = stbi__clamp((x1+t2) >> 17); + o[6] = stbi__clamp((x1-t2) >> 17); + o[2] = stbi__clamp((x2+t1) >> 17); + o[5] = stbi__clamp((x2-t1) >> 17); + o[3] = stbi__clamp((x3+t0) >> 17); + o[4] = stbi__clamp((x3-t0) >> 17); + } +} + +#ifdef STBI_SSE2 +// sse2 integer IDCT. not the fastest possible implementation but it +// produces bit-identical results to the generic C version so it's +// fully "transparent". +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + // This is constructed to match our regular (generic) integer IDCT exactly. + __m128i row0, row1, row2, row3, row4, row5, row6, row7; + __m128i tmp; + + // dot product constant: even elems=x, odd elems=y + #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) + + // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) + // out(1) = c1[even]*x + c1[odd]*y + #define dct_rot(out0,out1, x,y,c0,c1) \ + __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ + __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ + __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ + __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ + __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ + __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) + + // out = in << 12 (in 16-bit, out 32-bit) + #define dct_widen(out, in) \ + __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ + __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) + + // wide add + #define dct_wadd(out, a, b) \ + __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_add_epi32(a##_h, b##_h) + + // wide sub + #define dct_wsub(out, a, b) \ + __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) + + // butterfly a/b, add bias, then shift by "s" and pack + #define dct_bfly32o(out0, out1, a,b,bias,s) \ + { \ + __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ + __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ + dct_wadd(sum, abiased, b); \ + dct_wsub(dif, abiased, b); \ + out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ + out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ + } + + // 8-bit interleave step (for transposes) + #define dct_interleave8(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi8(a, b); \ + b = _mm_unpackhi_epi8(tmp, b) + + // 16-bit interleave step (for transposes) + #define dct_interleave16(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi16(a, b); \ + b = _mm_unpackhi_epi16(tmp, b) + + #define dct_pass(bias,shift) \ + { \ + /* even part */ \ + dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ + __m128i sum04 = _mm_add_epi16(row0, row4); \ + __m128i dif04 = _mm_sub_epi16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ + dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ + __m128i sum17 = _mm_add_epi16(row1, row7); \ + __m128i sum35 = _mm_add_epi16(row3, row5); \ + dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ + dct_wadd(x4, y0o, y4o); \ + dct_wadd(x5, y1o, y5o); \ + dct_wadd(x6, y2o, y5o); \ + dct_wadd(x7, y3o, y4o); \ + dct_bfly32o(row0,row7, x0,x7,bias,shift); \ + dct_bfly32o(row1,row6, x1,x6,bias,shift); \ + dct_bfly32o(row2,row5, x2,x5,bias,shift); \ + dct_bfly32o(row3,row4, x3,x4,bias,shift); \ + } + + __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); + __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); + __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); + __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); + __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); + __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); + __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); + __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); + + // rounding biases in column/row passes, see stbi__idct_block for explanation. + __m128i bias_0 = _mm_set1_epi32(512); + __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); + + // load + row0 = _mm_load_si128((const __m128i *) (data + 0*8)); + row1 = _mm_load_si128((const __m128i *) (data + 1*8)); + row2 = _mm_load_si128((const __m128i *) (data + 2*8)); + row3 = _mm_load_si128((const __m128i *) (data + 3*8)); + row4 = _mm_load_si128((const __m128i *) (data + 4*8)); + row5 = _mm_load_si128((const __m128i *) (data + 5*8)); + row6 = _mm_load_si128((const __m128i *) (data + 6*8)); + row7 = _mm_load_si128((const __m128i *) (data + 7*8)); + + // column pass + dct_pass(bias_0, 10); + + { + // 16bit 8x8 transpose pass 1 + dct_interleave16(row0, row4); + dct_interleave16(row1, row5); + dct_interleave16(row2, row6); + dct_interleave16(row3, row7); + + // transpose pass 2 + dct_interleave16(row0, row2); + dct_interleave16(row1, row3); + dct_interleave16(row4, row6); + dct_interleave16(row5, row7); + + // transpose pass 3 + dct_interleave16(row0, row1); + dct_interleave16(row2, row3); + dct_interleave16(row4, row5); + dct_interleave16(row6, row7); + } + + // row pass + dct_pass(bias_1, 17); + + { + // pack + __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 + __m128i p1 = _mm_packus_epi16(row2, row3); + __m128i p2 = _mm_packus_epi16(row4, row5); + __m128i p3 = _mm_packus_epi16(row6, row7); + + // 8bit 8x8 transpose pass 1 + dct_interleave8(p0, p2); // a0e0a1e1... + dct_interleave8(p1, p3); // c0g0c1g1... + + // transpose pass 2 + dct_interleave8(p0, p1); // a0c0e0g0... + dct_interleave8(p2, p3); // b0d0f0h0... + + // transpose pass 3 + dct_interleave8(p0, p2); // a0b0c0d0... + dct_interleave8(p1, p3); // a4b4c4d4... + + // store + _mm_storel_epi64((__m128i *) out, p0); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p2); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p1); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p3); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); + } + +#undef dct_const +#undef dct_rot +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_interleave8 +#undef dct_interleave16 +#undef dct_pass +} + +#endif // STBI_SSE2 + +#ifdef STBI_NEON + +// NEON integer IDCT. should produce bit-identical +// results to the generic C version. +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; + + int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); + int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); + int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); + int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); + int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); + int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); + int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); + int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); + int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); + int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); + int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); + int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); + +#define dct_long_mul(out, inq, coeff) \ + int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) + +#define dct_long_mac(out, acc, inq, coeff) \ + int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) + +#define dct_widen(out, inq) \ + int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ + int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) + +// wide add +#define dct_wadd(out, a, b) \ + int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vaddq_s32(a##_h, b##_h) + +// wide sub +#define dct_wsub(out, a, b) \ + int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vsubq_s32(a##_h, b##_h) + +// butterfly a/b, then shift using "shiftop" by "s" and pack +#define dct_bfly32o(out0,out1, a,b,shiftop,s) \ + { \ + dct_wadd(sum, a, b); \ + dct_wsub(dif, a, b); \ + out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ + out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ + } + +#define dct_pass(shiftop, shift) \ + { \ + /* even part */ \ + int16x8_t sum26 = vaddq_s16(row2, row6); \ + dct_long_mul(p1e, sum26, rot0_0); \ + dct_long_mac(t2e, p1e, row6, rot0_1); \ + dct_long_mac(t3e, p1e, row2, rot0_2); \ + int16x8_t sum04 = vaddq_s16(row0, row4); \ + int16x8_t dif04 = vsubq_s16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + int16x8_t sum15 = vaddq_s16(row1, row5); \ + int16x8_t sum17 = vaddq_s16(row1, row7); \ + int16x8_t sum35 = vaddq_s16(row3, row5); \ + int16x8_t sum37 = vaddq_s16(row3, row7); \ + int16x8_t sumodd = vaddq_s16(sum17, sum35); \ + dct_long_mul(p5o, sumodd, rot1_0); \ + dct_long_mac(p1o, p5o, sum17, rot1_1); \ + dct_long_mac(p2o, p5o, sum35, rot1_2); \ + dct_long_mul(p3o, sum37, rot2_0); \ + dct_long_mul(p4o, sum15, rot2_1); \ + dct_wadd(sump13o, p1o, p3o); \ + dct_wadd(sump24o, p2o, p4o); \ + dct_wadd(sump23o, p2o, p3o); \ + dct_wadd(sump14o, p1o, p4o); \ + dct_long_mac(x4, sump13o, row7, rot3_0); \ + dct_long_mac(x5, sump24o, row5, rot3_1); \ + dct_long_mac(x6, sump23o, row3, rot3_2); \ + dct_long_mac(x7, sump14o, row1, rot3_3); \ + dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ + dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ + dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ + dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ + } + + // load + row0 = vld1q_s16(data + 0*8); + row1 = vld1q_s16(data + 1*8); + row2 = vld1q_s16(data + 2*8); + row3 = vld1q_s16(data + 3*8); + row4 = vld1q_s16(data + 4*8); + row5 = vld1q_s16(data + 5*8); + row6 = vld1q_s16(data + 6*8); + row7 = vld1q_s16(data + 7*8); + + // add DC bias + row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); + + // column pass + dct_pass(vrshrn_n_s32, 10); + + // 16bit 8x8 transpose + { +// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. +// whether compilers actually get this is another story, sadly. +#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } +#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } + + // pass 1 + dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 + dct_trn16(row2, row3); + dct_trn16(row4, row5); + dct_trn16(row6, row7); + + // pass 2 + dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 + dct_trn32(row1, row3); + dct_trn32(row4, row6); + dct_trn32(row5, row7); + + // pass 3 + dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 + dct_trn64(row1, row5); + dct_trn64(row2, row6); + dct_trn64(row3, row7); + +#undef dct_trn16 +#undef dct_trn32 +#undef dct_trn64 + } + + // row pass + // vrshrn_n_s32 only supports shifts up to 16, we need + // 17. so do a non-rounding shift of 16 first then follow + // up with a rounding shift by 1. + dct_pass(vshrn_n_s32, 16); + + { + // pack and round + uint8x8_t p0 = vqrshrun_n_s16(row0, 1); + uint8x8_t p1 = vqrshrun_n_s16(row1, 1); + uint8x8_t p2 = vqrshrun_n_s16(row2, 1); + uint8x8_t p3 = vqrshrun_n_s16(row3, 1); + uint8x8_t p4 = vqrshrun_n_s16(row4, 1); + uint8x8_t p5 = vqrshrun_n_s16(row5, 1); + uint8x8_t p6 = vqrshrun_n_s16(row6, 1); + uint8x8_t p7 = vqrshrun_n_s16(row7, 1); + + // again, these can translate into one instruction, but often don't. +#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } +#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } + + // sadly can't use interleaved stores here since we only write + // 8 bytes to each scan line! + + // 8x8 8-bit transpose pass 1 + dct_trn8_8(p0, p1); + dct_trn8_8(p2, p3); + dct_trn8_8(p4, p5); + dct_trn8_8(p6, p7); + + // pass 2 + dct_trn8_16(p0, p2); + dct_trn8_16(p1, p3); + dct_trn8_16(p4, p6); + dct_trn8_16(p5, p7); + + // pass 3 + dct_trn8_32(p0, p4); + dct_trn8_32(p1, p5); + dct_trn8_32(p2, p6); + dct_trn8_32(p3, p7); + + // store + vst1_u8(out, p0); out += out_stride; + vst1_u8(out, p1); out += out_stride; + vst1_u8(out, p2); out += out_stride; + vst1_u8(out, p3); out += out_stride; + vst1_u8(out, p4); out += out_stride; + vst1_u8(out, p5); out += out_stride; + vst1_u8(out, p6); out += out_stride; + vst1_u8(out, p7); + +#undef dct_trn8_8 +#undef dct_trn8_16 +#undef dct_trn8_32 + } + +#undef dct_long_mul +#undef dct_long_mac +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_pass +} + +#endif // STBI_NEON + +#define STBI__MARKER_none 0xff +// if there's a pending marker from the entropy stream, return that +// otherwise, fetch from the stream and get a marker. if there's no +// marker, return 0xff, which is never a valid marker value +static stbi_uc stbi__get_marker(stbi__jpeg *j) +{ + stbi_uc x; + if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } + x = stbi__get8(j->s); + if (x != 0xff) return STBI__MARKER_none; + while (x == 0xff) + x = stbi__get8(j->s); // consume repeated 0xff fill bytes + return x; +} + +// in each scan, we'll have scan_n components, and the order +// of the components is specified by order[] +#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) + +// after a restart interval, stbi__jpeg_reset the entropy decoder and +// the dc prediction +static void stbi__jpeg_reset(stbi__jpeg *j) +{ + j->code_bits = 0; + j->code_buffer = 0; + j->nomore = 0; + j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = j->img_comp[3].dc_pred = 0; + j->marker = STBI__MARKER_none; + j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; + j->eob_run = 0; + // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, + // since we don't even allow 1<<30 pixels +} + +static int stbi__parse_entropy_coded_data(stbi__jpeg *z) +{ + stbi__jpeg_reset(z); + if (!z->progressive) { + if (z->scan_n == 1) { + int i,j; + STBI_SIMD_ALIGN(short, data[64]); + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + STBI_SIMD_ALIGN(short, data[64]); + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x)*8; + int y2 = (j*z->img_comp[n].v + y)*8; + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } else { + if (z->scan_n == 1) { + int i,j; + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + if (z->spec_start == 0) { + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } else { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) + return 0; + } + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x); + int y2 = (j*z->img_comp[n].v + y); + short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } +} + +static void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant) +{ + int i; + for (i=0; i < 64; ++i) + data[i] *= dequant[i]; +} + +static void stbi__jpeg_finish(stbi__jpeg *z) +{ + if (z->progressive) { + // dequantize and idct the data + int i,j,n; + for (n=0; n < z->s->img_n; ++n) { + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + } + } + } + } +} + +static int stbi__process_marker(stbi__jpeg *z, int m) +{ + int L; + switch (m) { + case STBI__MARKER_none: // no marker found + return stbi__err("expected marker","Corrupt JPEG"); + + case 0xDD: // DRI - specify restart interval + if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); + z->restart_interval = stbi__get16be(z->s); + return 1; + + case 0xDB: // DQT - define quantization table + L = stbi__get16be(z->s)-2; + while (L > 0) { + int q = stbi__get8(z->s); + int p = q >> 4, sixteen = (p != 0); + int t = q & 15,i; + if (p != 0 && p != 1) return stbi__err("bad DQT type","Corrupt JPEG"); + if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); + + for (i=0; i < 64; ++i) + z->dequant[t][stbi__jpeg_dezigzag[i]] = sixteen ? stbi__get16be(z->s) : stbi__get8(z->s); + L -= (sixteen ? 129 : 65); + } + return L==0; + + case 0xC4: // DHT - define huffman table + L = stbi__get16be(z->s)-2; + while (L > 0) { + stbi_uc *v; + int sizes[16],i,n=0; + int q = stbi__get8(z->s); + int tc = q >> 4; + int th = q & 15; + if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); + for (i=0; i < 16; ++i) { + sizes[i] = stbi__get8(z->s); + n += sizes[i]; + } + L -= 17; + if (tc == 0) { + if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; + v = z->huff_dc[th].values; + } else { + if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; + v = z->huff_ac[th].values; + } + for (i=0; i < n; ++i) + v[i] = stbi__get8(z->s); + if (tc != 0) + stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); + L -= n; + } + return L==0; + } + + // check for comment block or APP blocks + if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { + L = stbi__get16be(z->s); + if (L < 2) { + if (m == 0xFE) + return stbi__err("bad COM len","Corrupt JPEG"); + else + return stbi__err("bad APP len","Corrupt JPEG"); + } + L -= 2; + + if (m == 0xE0 && L >= 5) { // JFIF APP0 segment + static const unsigned char tag[5] = {'J','F','I','F','\0'}; + int ok = 1; + int i; + for (i=0; i < 5; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 5; + if (ok) + z->jfif = 1; + } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment + static const unsigned char tag[6] = {'A','d','o','b','e','\0'}; + int ok = 1; + int i; + for (i=0; i < 6; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 6; + if (ok) { + stbi__get8(z->s); // version + stbi__get16be(z->s); // flags0 + stbi__get16be(z->s); // flags1 + z->app14_color_transform = stbi__get8(z->s); // color transform + L -= 6; + } + } + + stbi__skip(z->s, L); + return 1; + } + + return stbi__err("unknown marker","Corrupt JPEG"); +} + +// after we see SOS +static int stbi__process_scan_header(stbi__jpeg *z) +{ + int i; + int Ls = stbi__get16be(z->s); + z->scan_n = stbi__get8(z->s); + if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG"); + if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); + for (i=0; i < z->scan_n; ++i) { + int id = stbi__get8(z->s), which; + int q = stbi__get8(z->s); + for (which = 0; which < z->s->img_n; ++which) + if (z->img_comp[which].id == id) + break; + if (which == z->s->img_n) return 0; // no match + z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); + z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); + z->order[i] = which; + } + + { + int aa; + z->spec_start = stbi__get8(z->s); + z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 + aa = stbi__get8(z->s); + z->succ_high = (aa >> 4); + z->succ_low = (aa & 15); + if (z->progressive) { + if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) + return stbi__err("bad SOS", "Corrupt JPEG"); + } else { + if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); + if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); + z->spec_end = 63; + } + } + + return 1; +} + +static int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why) +{ + int i; + for (i=0; i < ncomp; ++i) { + if (z->img_comp[i].raw_data) { + STBI_FREE(z->img_comp[i].raw_data); + z->img_comp[i].raw_data = NULL; + z->img_comp[i].data = NULL; + } + if (z->img_comp[i].raw_coeff) { + STBI_FREE(z->img_comp[i].raw_coeff); + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].coeff = 0; + } + if (z->img_comp[i].linebuf) { + STBI_FREE(z->img_comp[i].linebuf); + z->img_comp[i].linebuf = NULL; + } + } + return why; +} + +static int stbi__process_frame_header(stbi__jpeg *z, int scan) +{ + stbi__context *s = z->s; + int Lf,p,i,q, h_max=1,v_max=1,c; + Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG + p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline + s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG + s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires + c = stbi__get8(s); + if (c != 3 && c != 1 && c != 4) return stbi__err("bad component count","Corrupt JPEG"); + s->img_n = c; + for (i=0; i < c; ++i) { + z->img_comp[i].data = NULL; + z->img_comp[i].linebuf = NULL; + } + + if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); + + z->rgb = 0; + for (i=0; i < s->img_n; ++i) { + static unsigned char rgb[3] = { 'R', 'G', 'B' }; + z->img_comp[i].id = stbi__get8(s); + if (s->img_n == 3 && z->img_comp[i].id == rgb[i]) + ++z->rgb; + q = stbi__get8(s); + z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); + z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); + z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); + } + + if (scan != STBI__SCAN_load) return 1; + + if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err("too large", "Image too large to decode"); + + for (i=0; i < s->img_n; ++i) { + if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; + if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; + } + + // compute interleaved mcu info + z->img_h_max = h_max; + z->img_v_max = v_max; + z->img_mcu_w = h_max * 8; + z->img_mcu_h = v_max * 8; + // these sizes can't be more than 17 bits + z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; + z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; + + for (i=0; i < s->img_n; ++i) { + // number of effective pixels (e.g. for non-interleaved MCU) + z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; + z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; + // to simplify generation, we'll allocate enough memory to decode + // the bogus oversized data from using interleaved MCUs and their + // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't + // discard the extra data until colorspace conversion + // + // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier) + // so these muls can't overflow with 32-bit ints (which we require) + z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; + z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; + z->img_comp[i].coeff = 0; + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].linebuf = NULL; + z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15); + if (z->img_comp[i].raw_data == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + // align blocks for idct using mmx/sse + z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); + if (z->progressive) { + // w2, h2 are multiples of 8 (see above) + z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8; + z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8; + z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15); + if (z->img_comp[i].raw_coeff == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); + } + } + + return 1; +} + +// use comparisons since in some cases we handle more than one case (e.g. SOF) +#define stbi__DNL(x) ((x) == 0xdc) +#define stbi__SOI(x) ((x) == 0xd8) +#define stbi__EOI(x) ((x) == 0xd9) +#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) +#define stbi__SOS(x) ((x) == 0xda) + +#define stbi__SOF_progressive(x) ((x) == 0xc2) + +static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) +{ + int m; + z->jfif = 0; + z->app14_color_transform = -1; // valid values are 0,1,2 + z->marker = STBI__MARKER_none; // initialize cached marker to empty + m = stbi__get_marker(z); + if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); + if (scan == STBI__SCAN_type) return 1; + m = stbi__get_marker(z); + while (!stbi__SOF(m)) { + if (!stbi__process_marker(z,m)) return 0; + m = stbi__get_marker(z); + while (m == STBI__MARKER_none) { + // some files have extra padding after their blocks, so ok, we'll scan + if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); + m = stbi__get_marker(z); + } + } + z->progressive = stbi__SOF_progressive(m); + if (!stbi__process_frame_header(z, scan)) return 0; + return 1; +} + +// decode image to YCbCr format +static int stbi__decode_jpeg_image(stbi__jpeg *j) +{ + int m; + for (m = 0; m < 4; m++) { + j->img_comp[m].raw_data = NULL; + j->img_comp[m].raw_coeff = NULL; + } + j->restart_interval = 0; + if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; + m = stbi__get_marker(j); + while (!stbi__EOI(m)) { + if (stbi__SOS(m)) { + if (!stbi__process_scan_header(j)) return 0; + if (!stbi__parse_entropy_coded_data(j)) return 0; + if (j->marker == STBI__MARKER_none ) { + // handle 0s at the end of image data from IP Kamera 9060 + while (!stbi__at_eof(j->s)) { + int x = stbi__get8(j->s); + if (x == 255) { + j->marker = stbi__get8(j->s); + break; + } + } + // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 + } + } else if (stbi__DNL(m)) { + int Ld = stbi__get16be(j->s); + stbi__uint32 NL = stbi__get16be(j->s); + if (Ld != 4) stbi__err("bad DNL len", "Corrupt JPEG"); + if (NL != j->s->img_y) stbi__err("bad DNL height", "Corrupt JPEG"); + } else { + if (!stbi__process_marker(j, m)) return 0; + } + m = stbi__get_marker(j); + } + if (j->progressive) + stbi__jpeg_finish(j); + return 1; +} + +// static jfif-centered resampling (across block boundaries) + +typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, + int w, int hs); + +#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) + +static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + STBI_NOTUSED(out); + STBI_NOTUSED(in_far); + STBI_NOTUSED(w); + STBI_NOTUSED(hs); + return in_near; +} + +static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples vertically for every one in input + int i; + STBI_NOTUSED(hs); + for (i=0; i < w; ++i) + out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); + return out; +} + +static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples horizontally for every one in input + int i; + stbi_uc *input = in_near; + + if (w == 1) { + // if only one sample, can't do any interpolation + out[0] = out[1] = input[0]; + return out; + } + + out[0] = input[0]; + out[1] = stbi__div4(input[0]*3 + input[1] + 2); + for (i=1; i < w-1; ++i) { + int n = 3*input[i]+2; + out[i*2+0] = stbi__div4(n+input[i-1]); + out[i*2+1] = stbi__div4(n+input[i+1]); + } + out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); + out[i*2+1] = input[w-1]; + + STBI_NOTUSED(in_far); + STBI_NOTUSED(hs); + + return out; +} + +#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) + +static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i,t0,t1; + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + out[0] = stbi__div4(t1+2); + for (i=1; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i=0,t0,t1; + + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + // process groups of 8 pixels for as long as we can. + // note we can't handle the last pixel in a row in this loop + // because we need to handle the filter boundary conditions. + for (; i < ((w-1) & ~7); i += 8) { +#if defined(STBI_SSE2) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + __m128i zero = _mm_setzero_si128(); + __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); + __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); + __m128i farw = _mm_unpacklo_epi8(farb, zero); + __m128i nearw = _mm_unpacklo_epi8(nearb, zero); + __m128i diff = _mm_sub_epi16(farw, nearw); + __m128i nears = _mm_slli_epi16(nearw, 2); + __m128i curr = _mm_add_epi16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + __m128i prv0 = _mm_slli_si128(curr, 2); + __m128i nxt0 = _mm_srli_si128(curr, 2); + __m128i prev = _mm_insert_epi16(prv0, t1, 0); + __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + __m128i bias = _mm_set1_epi16(8); + __m128i curs = _mm_slli_epi16(curr, 2); + __m128i prvd = _mm_sub_epi16(prev, curr); + __m128i nxtd = _mm_sub_epi16(next, curr); + __m128i curb = _mm_add_epi16(curs, bias); + __m128i even = _mm_add_epi16(prvd, curb); + __m128i odd = _mm_add_epi16(nxtd, curb); + + // interleave even and odd pixels, then undo scaling. + __m128i int0 = _mm_unpacklo_epi16(even, odd); + __m128i int1 = _mm_unpackhi_epi16(even, odd); + __m128i de0 = _mm_srli_epi16(int0, 4); + __m128i de1 = _mm_srli_epi16(int1, 4); + + // pack and write output + __m128i outv = _mm_packus_epi16(de0, de1); + _mm_storeu_si128((__m128i *) (out + i*2), outv); +#elif defined(STBI_NEON) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + uint8x8_t farb = vld1_u8(in_far + i); + uint8x8_t nearb = vld1_u8(in_near + i); + int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); + int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); + int16x8_t curr = vaddq_s16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + int16x8_t prv0 = vextq_s16(curr, curr, 7); + int16x8_t nxt0 = vextq_s16(curr, curr, 1); + int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); + int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + int16x8_t curs = vshlq_n_s16(curr, 2); + int16x8_t prvd = vsubq_s16(prev, curr); + int16x8_t nxtd = vsubq_s16(next, curr); + int16x8_t even = vaddq_s16(curs, prvd); + int16x8_t odd = vaddq_s16(curs, nxtd); + + // undo scaling and round, then store with even/odd phases interleaved + uint8x8x2_t o; + o.val[0] = vqrshrun_n_s16(even, 4); + o.val[1] = vqrshrun_n_s16(odd, 4); + vst2_u8(out + i*2, o); +#endif + + // "previous" value for next iter + t1 = 3*in_near[i+7] + in_far[i+7]; + } + + t0 = t1; + t1 = 3*in_near[i] + in_far[i]; + out[i*2] = stbi__div16(3*t1 + t0 + 8); + + for (++i; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} +#endif + +static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // resample with nearest-neighbor + int i,j; + STBI_NOTUSED(in_far); + for (i=0; i < w; ++i) + for (j=0; j < hs; ++j) + out[i*hs+j] = in_near[i]; + return out; +} + +// this is a reduced-precision calculation of YCbCr-to-RGB introduced +// to make sure the code produces the same results in both SIMD and scalar +#define stbi__float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) +static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) +{ + int i = 0; + +#ifdef STBI_SSE2 + // step == 3 is pretty ugly on the final interleave, and i'm not convinced + // it's useful in practice (you wouldn't use it for textures, for example). + // so just accelerate step == 4 case. + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + __m128i signflip = _mm_set1_epi8(-0x80); + __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); + __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); + __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); + __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); + __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); + __m128i xw = _mm_set1_epi16(255); // alpha channel + + for (; i+7 < count; i += 8) { + // load + __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); + __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); + __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); + __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 + __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 + + // unpack to short (and left-shift cr, cb by 8) + __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); + __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); + __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); + + // color transform + __m128i yws = _mm_srli_epi16(yw, 4); + __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); + __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); + __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); + __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); + __m128i rws = _mm_add_epi16(cr0, yws); + __m128i gwt = _mm_add_epi16(cb0, yws); + __m128i bws = _mm_add_epi16(yws, cb1); + __m128i gws = _mm_add_epi16(gwt, cr1); + + // descale + __m128i rw = _mm_srai_epi16(rws, 4); + __m128i bw = _mm_srai_epi16(bws, 4); + __m128i gw = _mm_srai_epi16(gws, 4); + + // back to byte, set up for transpose + __m128i brb = _mm_packus_epi16(rw, bw); + __m128i gxb = _mm_packus_epi16(gw, xw); + + // transpose to interleave channels + __m128i t0 = _mm_unpacklo_epi8(brb, gxb); + __m128i t1 = _mm_unpackhi_epi8(brb, gxb); + __m128i o0 = _mm_unpacklo_epi16(t0, t1); + __m128i o1 = _mm_unpackhi_epi16(t0, t1); + + // store + _mm_storeu_si128((__m128i *) (out + 0), o0); + _mm_storeu_si128((__m128i *) (out + 16), o1); + out += 32; + } + } +#endif + +#ifdef STBI_NEON + // in this version, step=3 support would be easy to add. but is there demand? + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + uint8x8_t signflip = vdup_n_u8(0x80); + int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); + int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); + int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); + int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); + + for (; i+7 < count; i += 8) { + // load + uint8x8_t y_bytes = vld1_u8(y + i); + uint8x8_t cr_bytes = vld1_u8(pcr + i); + uint8x8_t cb_bytes = vld1_u8(pcb + i); + int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); + int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); + + // expand to s16 + int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); + int16x8_t crw = vshll_n_s8(cr_biased, 7); + int16x8_t cbw = vshll_n_s8(cb_biased, 7); + + // color transform + int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); + int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); + int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); + int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); + int16x8_t rws = vaddq_s16(yws, cr0); + int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); + int16x8_t bws = vaddq_s16(yws, cb1); + + // undo scaling, round, convert to byte + uint8x8x4_t o; + o.val[0] = vqrshrun_n_s16(rws, 4); + o.val[1] = vqrshrun_n_s16(gws, 4); + o.val[2] = vqrshrun_n_s16(bws, 4); + o.val[3] = vdup_n_u8(255); + + // store, interleaving r/g/b/a + vst4_u8(out, o); + out += 8*4; + } + } +#endif + + for (; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#endif + +// set up the kernels +static void stbi__setup_jpeg(stbi__jpeg *j) +{ + j->idct_block_kernel = stbi__idct_block; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; + +#ifdef STBI_SSE2 + if (stbi__sse2_available()) { + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; + } +#endif + +#ifdef STBI_NEON + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; +#endif +} + +// clean up the temporary component buffers +static void stbi__cleanup_jpeg(stbi__jpeg *j) +{ + stbi__free_jpeg_components(j, j->s->img_n, 0); +} + +typedef struct +{ + resample_row_func resample; + stbi_uc *line0,*line1; + int hs,vs; // expansion factor in each axis + int w_lores; // horizontal pixels pre-expansion + int ystep; // how far through vertical expansion we are + int ypos; // which pre-expansion row we're on +} stbi__resample; + +// fast 0..255 * 0..255 => 0..255 rounded multiplication +static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y) +{ + unsigned int t = x*y + 128; + return (stbi_uc) ((t + (t >>8)) >> 8); +} + +static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) +{ + int n, decode_n, is_rgb; + z->s->img_n = 0; // make stbi__cleanup_jpeg safe + + // validate req_comp + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + + // load a jpeg image from whichever source, but leave in YCbCr format + if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } + + // determine actual number of components to generate + n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1; + + is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif)); + + if (z->s->img_n == 3 && n < 3 && !is_rgb) + decode_n = 1; + else + decode_n = z->s->img_n; + + // resample and color-convert + { + int k; + unsigned int i,j; + stbi_uc *output; + stbi_uc *coutput[4]; + + stbi__resample res_comp[4]; + + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + + // allocate line buffer big enough for upsampling off the edges + // with upsample factor of 4 + z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); + if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + r->hs = z->img_h_max / z->img_comp[k].h; + r->vs = z->img_v_max / z->img_comp[k].v; + r->ystep = r->vs >> 1; + r->w_lores = (z->s->img_x + r->hs-1) / r->hs; + r->ypos = 0; + r->line0 = r->line1 = z->img_comp[k].data; + + if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; + else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; + else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; + else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; + else r->resample = stbi__resample_row_generic; + } + + // can't error after this so, this is safe + output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1); + if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + // now go ahead and resample + for (j=0; j < z->s->img_y; ++j) { + stbi_uc *out = output + n * z->s->img_x * j; + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + int y_bot = r->ystep >= (r->vs >> 1); + coutput[k] = r->resample(z->img_comp[k].linebuf, + y_bot ? r->line1 : r->line0, + y_bot ? r->line0 : r->line1, + r->w_lores, r->hs); + if (++r->ystep >= r->vs) { + r->ystep = 0; + r->line0 = r->line1; + if (++r->ypos < z->img_comp[k].y) + r->line1 += z->img_comp[k].w2; + } + } + if (n >= 3) { + stbi_uc *y = coutput[0]; + if (z->s->img_n == 3) { + if (is_rgb) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = y[i]; + out[1] = coutput[1][i]; + out[2] = coutput[2][i]; + out[3] = 255; + out += n; + } + } else { + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else if (z->s->img_n == 4) { + if (z->app14_color_transform == 0) { // CMYK + for (i=0; i < z->s->img_x; ++i) { + stbi_uc k = coutput[3][i]; + out[0] = stbi__blinn_8x8(coutput[0][i], k); + out[1] = stbi__blinn_8x8(coutput[1][i], k); + out[2] = stbi__blinn_8x8(coutput[2][i], k); + out[3] = 255; + out += n; + } + } else if (z->app14_color_transform == 2) { // YCCK + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + for (i=0; i < z->s->img_x; ++i) { + stbi_uc k = coutput[3][i]; + out[0] = stbi__blinn_8x8(255 - out[0], k); + out[1] = stbi__blinn_8x8(255 - out[1], k); + out[2] = stbi__blinn_8x8(255 - out[2], k); + out += n; + } + } else { // YCbCr + alpha? Ignore the fourth channel for now + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else + for (i=0; i < z->s->img_x; ++i) { + out[0] = out[1] = out[2] = y[i]; + out[3] = 255; // not used if n==3 + out += n; + } + } else { + if (is_rgb) { + if (n == 1) + for (i=0; i < z->s->img_x; ++i) + *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + else { + for (i=0; i < z->s->img_x; ++i, out += 2) { + out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + out[1] = 255; + } + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 0) { + for (i=0; i < z->s->img_x; ++i) { + stbi_uc k = coutput[3][i]; + stbi_uc r = stbi__blinn_8x8(coutput[0][i], k); + stbi_uc g = stbi__blinn_8x8(coutput[1][i], k); + stbi_uc b = stbi__blinn_8x8(coutput[2][i], k); + out[0] = stbi__compute_y(r, g, b); + out[1] = 255; + out += n; + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 2) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]); + out[1] = 255; + out += n; + } + } else { + stbi_uc *y = coutput[0]; + if (n == 1) + for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; + else + for (i=0; i < z->s->img_x; ++i) *out++ = y[i], *out++ = 255; + } + } + } + stbi__cleanup_jpeg(z); + *out_x = z->s->img_x; + *out_y = z->s->img_y; + if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output + return output; + } +} + +static void * STBI_FORCE_STACK_ALIGN stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + unsigned char* result; + stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); + STBI_NOTUSED(ri); + j->s = s; + stbi__setup_jpeg(j); + result = load_jpeg_image(j, x,y,comp,req_comp); + STBI_FREE(j); + return result; +} + +static int stbi__jpeg_test(stbi__context *s) +{ + int r; + stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg)); + j->s = s; + stbi__setup_jpeg(j); + r = stbi__decode_jpeg_header(j, STBI__SCAN_type); + stbi__rewind(s); + STBI_FREE(j); + return r; +} + +static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) +{ + if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { + stbi__rewind( j->s ); + return 0; + } + if (x) *x = j->s->img_x; + if (y) *y = j->s->img_y; + if (comp) *comp = j->s->img_n >= 3 ? 3 : 1; + return 1; +} + +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) +{ + int result; + stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); + j->s = s; + result = stbi__jpeg_info_raw(j, x, y, comp); + STBI_FREE(j); + return result; +} +#endif + +// public domain zlib decode v0.2 Sean Barrett 2006-11-18 +// simple implementation +// - all input must be provided in an upfront buffer +// - all output is written to a single output buffer (can malloc/realloc) +// performance +// - fast huffman + +#ifndef STBI_NO_ZLIB + +// fast-way is faster to check than jpeg huffman, but slow way is slower +#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables +#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) + +// zlib-style huffman encoding +// (jpegs packs from left, zlib from right, so can't share code) +typedef struct +{ + stbi__uint16 fast[1 << STBI__ZFAST_BITS]; + stbi__uint16 firstcode[16]; + int maxcode[17]; + stbi__uint16 firstsymbol[16]; + stbi_uc size[288]; + stbi__uint16 value[288]; +} stbi__zhuffman; + +stbi_inline static int stbi__bitreverse16(int n) +{ + n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); + n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); + n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); + n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); + return n; +} + +stbi_inline static int stbi__bit_reverse(int v, int bits) +{ + STBI_ASSERT(bits <= 16); + // to bit reverse n bits, reverse 16 and shift + // e.g. 11 bits, bit reverse and shift away 5 + return stbi__bitreverse16(v) >> (16-bits); +} + +static int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num) +{ + int i,k=0; + int code, next_code[16], sizes[17]; + + // DEFLATE spec for generating codes + memset(sizes, 0, sizeof(sizes)); + memset(z->fast, 0, sizeof(z->fast)); + for (i=0; i < num; ++i) + ++sizes[sizelist[i]]; + sizes[0] = 0; + for (i=1; i < 16; ++i) + if (sizes[i] > (1 << i)) + return stbi__err("bad sizes", "Corrupt PNG"); + code = 0; + for (i=1; i < 16; ++i) { + next_code[i] = code; + z->firstcode[i] = (stbi__uint16) code; + z->firstsymbol[i] = (stbi__uint16) k; + code = (code + sizes[i]); + if (sizes[i]) + if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); + z->maxcode[i] = code << (16-i); // preshift for inner loop + code <<= 1; + k += sizes[i]; + } + z->maxcode[16] = 0x10000; // sentinel + for (i=0; i < num; ++i) { + int s = sizelist[i]; + if (s) { + int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; + stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); + z->size [c] = (stbi_uc ) s; + z->value[c] = (stbi__uint16) i; + if (s <= STBI__ZFAST_BITS) { + int j = stbi__bit_reverse(next_code[s],s); + while (j < (1 << STBI__ZFAST_BITS)) { + z->fast[j] = fastv; + j += (1 << s); + } + } + ++next_code[s]; + } + } + return 1; +} + +// zlib-from-memory implementation for PNG reading +// because PNG allows splitting the zlib stream arbitrarily, +// and it's annoying structurally to have PNG call ZLIB call PNG, +// we require PNG read all the IDATs and combine them into a single +// memory buffer + +typedef struct +{ + stbi_uc *zbuffer, *zbuffer_end; + int num_bits; + stbi__uint32 code_buffer; + + char *zout; + char *zout_start; + char *zout_end; + int z_expandable; + + stbi__zhuffman z_length, z_distance; +} stbi__zbuf; + +stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) +{ + if (z->zbuffer >= z->zbuffer_end) return 0; + return *z->zbuffer++; +} + +static void stbi__fill_bits(stbi__zbuf *z) +{ + do { + STBI_ASSERT(z->code_buffer < (1U << z->num_bits)); + z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; + z->num_bits += 8; + } while (z->num_bits <= 24); +} + +stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) +{ + unsigned int k; + if (z->num_bits < n) stbi__fill_bits(z); + k = z->code_buffer & ((1 << n) - 1); + z->code_buffer >>= n; + z->num_bits -= n; + return k; +} + +static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s,k; + // not resolved by fast table, so compute it the slow way + // use jpeg approach, which requires MSbits at top + k = stbi__bit_reverse(a->code_buffer, 16); + for (s=STBI__ZFAST_BITS+1; ; ++s) + if (k < z->maxcode[s]) + break; + if (s == 16) return -1; // invalid code! + // code size is s, so: + b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; + STBI_ASSERT(z->size[b] == s); + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; +} + +stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s; + if (a->num_bits < 16) stbi__fill_bits(a); + b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; + if (b) { + s = b >> 9; + a->code_buffer >>= s; + a->num_bits -= s; + return b & 511; + } + return stbi__zhuffman_decode_slowpath(a, z); +} + +static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes +{ + char *q; + int cur, limit, old_limit; + z->zout = zout; + if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); + cur = (int) (z->zout - z->zout_start); + limit = old_limit = (int) (z->zout_end - z->zout_start); + while (cur + n > limit) + limit *= 2; + q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); + STBI_NOTUSED(old_limit); + if (q == NULL) return stbi__err("outofmem", "Out of memory"); + z->zout_start = q; + z->zout = q + cur; + z->zout_end = q + limit; + return 1; +} + +static int stbi__zlength_base[31] = { + 3,4,5,6,7,8,9,10,11,13, + 15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,0,0 }; + +static int stbi__zlength_extra[31]= +{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + +static int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, +257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + +static int stbi__zdist_extra[32] = +{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +static int stbi__parse_huffman_block(stbi__zbuf *a) +{ + char *zout = a->zout; + for(;;) { + int z = stbi__zhuffman_decode(a, &a->z_length); + if (z < 256) { + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes + if (zout >= a->zout_end) { + if (!stbi__zexpand(a, zout, 1)) return 0; + zout = a->zout; + } + *zout++ = (char) z; + } else { + stbi_uc *p; + int len,dist; + if (z == 256) { + a->zout = zout; + return 1; + } + z -= 257; + len = stbi__zlength_base[z]; + if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); + z = stbi__zhuffman_decode(a, &a->z_distance); + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); + dist = stbi__zdist_base[z]; + if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); + if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); + if (zout + len > a->zout_end) { + if (!stbi__zexpand(a, zout, len)) return 0; + zout = a->zout; + } + p = (stbi_uc *) (zout - dist); + if (dist == 1) { // run of one byte; common in images. + stbi_uc v = *p; + if (len) { do *zout++ = v; while (--len); } + } else { + if (len) { do *zout++ = *p++; while (--len); } + } + } + } +} + +static int stbi__compute_huffman_codes(stbi__zbuf *a) +{ + static stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + stbi__zhuffman z_codelength; + stbi_uc lencodes[286+32+137];//padding for maximum single op + stbi_uc codelength_sizes[19]; + int i,n; + + int hlit = stbi__zreceive(a,5) + 257; + int hdist = stbi__zreceive(a,5) + 1; + int hclen = stbi__zreceive(a,4) + 4; + int ntot = hlit + hdist; + + memset(codelength_sizes, 0, sizeof(codelength_sizes)); + for (i=0; i < hclen; ++i) { + int s = stbi__zreceive(a,3); + codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; + } + if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; + + n = 0; + while (n < ntot) { + int c = stbi__zhuffman_decode(a, &z_codelength); + if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); + if (c < 16) + lencodes[n++] = (stbi_uc) c; + else { + stbi_uc fill = 0; + if (c == 16) { + c = stbi__zreceive(a,2)+3; + if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG"); + fill = lencodes[n-1]; + } else if (c == 17) + c = stbi__zreceive(a,3)+3; + else { + STBI_ASSERT(c == 18); + c = stbi__zreceive(a,7)+11; + } + if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG"); + memset(lencodes+n, fill, c); + n += c; + } + } + if (n != ntot) return stbi__err("bad codelengths","Corrupt PNG"); + if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; + return 1; +} + +static int stbi__parse_uncompressed_block(stbi__zbuf *a) +{ + stbi_uc header[4]; + int len,nlen,k; + if (a->num_bits & 7) + stbi__zreceive(a, a->num_bits & 7); // discard + // drain the bit-packed data into header + k = 0; + while (a->num_bits > 0) { + header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check + a->code_buffer >>= 8; + a->num_bits -= 8; + } + STBI_ASSERT(a->num_bits == 0); + // now fill header the normal way + while (k < 4) + header[k++] = stbi__zget8(a); + len = header[1] * 256 + header[0]; + nlen = header[3] * 256 + header[2]; + if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); + if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); + if (a->zout + len > a->zout_end) + if (!stbi__zexpand(a, a->zout, len)) return 0; + memcpy(a->zout, a->zbuffer, len); + a->zbuffer += len; + a->zout += len; + return 1; +} + +static int stbi__parse_zlib_header(stbi__zbuf *a) +{ + int cmf = stbi__zget8(a); + int cm = cmf & 15; + /* int cinfo = cmf >> 4; */ + int flg = stbi__zget8(a); + if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png + if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png + // window = 1 << (8 + cinfo)... but who cares, we fully buffer output + return 1; +} + +static const stbi_uc stbi__zdefault_length[288] = +{ + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8 +}; +static const stbi_uc stbi__zdefault_distance[32] = +{ + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5 +}; +/* +Init algorithm: +{ + int i; // use <= to match clearly with spec + for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; + for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; + for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; + for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; + + for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; +} +*/ + +static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) +{ + int final, type; + if (parse_header) + if (!stbi__parse_zlib_header(a)) return 0; + a->num_bits = 0; + a->code_buffer = 0; + do { + final = stbi__zreceive(a,1); + type = stbi__zreceive(a,2); + if (type == 0) { + if (!stbi__parse_uncompressed_block(a)) return 0; + } else if (type == 3) { + return 0; + } else { + if (type == 1) { + // use fixed code lengths + if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , 288)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; + } else { + if (!stbi__compute_huffman_codes(a)) return 0; + } + if (!stbi__parse_huffman_block(a)) return 0; + } + } while (!final); + return 1; +} + +static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) +{ + a->zout_start = obuf; + a->zout = obuf; + a->zout_end = obuf + olen; + a->z_expandable = exp; + + return stbi__parse_zlib(a, parse_header); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) +{ + return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(16384); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer+len; + if (stbi__do_zlib(&a, p, 16384, 1, 0)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) + return (int) (a.zout - a.zout_start); + else + return -1; +} +#endif + +// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 +// simple implementation +// - only 8-bit samples +// - no CRC checking +// - allocates lots of intermediate memory +// - avoids problem of streaming data between subsystems +// - avoids explicit window management +// performance +// - uses stb_zlib, a PD zlib implementation with fast huffman decoding + +#ifndef STBI_NO_PNG +typedef struct +{ + stbi__uint32 length; + stbi__uint32 type; +} stbi__pngchunk; + +static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) +{ + stbi__pngchunk c; + c.length = stbi__get32be(s); + c.type = stbi__get32be(s); + return c; +} + +static int stbi__check_png_header(stbi__context *s) +{ + static stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; + int i; + for (i=0; i < 8; ++i) + if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); + return 1; +} + +typedef struct +{ + stbi__context *s; + stbi_uc *idata, *expanded, *out; + int depth; +} stbi__png; + + +enum { + STBI__F_none=0, + STBI__F_sub=1, + STBI__F_up=2, + STBI__F_avg=3, + STBI__F_paeth=4, + // synthetic filters used for first scanline to avoid needing a dummy row of 0s + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static stbi_uc first_row_filter[5] = +{ + STBI__F_none, + STBI__F_sub, + STBI__F_none, + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static int stbi__paeth(int a, int b, int c) +{ + int p = a + b - c; + int pa = abs(p-a); + int pb = abs(p-b); + int pc = abs(p-c); + if (pa <= pb && pa <= pc) return a; + if (pb <= pc) return b; + return c; +} + +static stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; + +// create the png data from post-deflated data +static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) +{ + int bytes = (depth == 16? 2 : 1); + stbi__context *s = a->s; + stbi__uint32 i,j,stride = x*out_n*bytes; + stbi__uint32 img_len, img_width_bytes; + int k; + int img_n = s->img_n; // copy it into a local for later + + int output_bytes = out_n*bytes; + int filter_bytes = img_n*bytes; + int width = x; + + STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); + a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into + if (!a->out) return stbi__err("outofmem", "Out of memory"); + + img_width_bytes = (((img_n * x * depth) + 7) >> 3); + img_len = (img_width_bytes + 1) * y; + if (s->img_x == x && s->img_y == y) { + if (raw_len != img_len) return stbi__err("not enough pixels","Corrupt PNG"); + } else { // interlaced: + if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); + } + + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *prior; + int filter = *raw++; + + if (filter > 4) + return stbi__err("invalid filter","Corrupt PNG"); + + if (depth < 8) { + STBI_ASSERT(img_width_bytes <= x); + cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place + filter_bytes = 1; + width = img_width_bytes; + } + prior = cur - stride; // bugfix: need to compute this after 'cur +=' computation above + + // if first row, use special filter that doesn't sample previous row + if (j == 0) filter = first_row_filter[filter]; + + // handle first byte explicitly + for (k=0; k < filter_bytes; ++k) { + switch (filter) { + case STBI__F_none : cur[k] = raw[k]; break; + case STBI__F_sub : cur[k] = raw[k]; break; + case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; + case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break; + case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break; + case STBI__F_avg_first : cur[k] = raw[k]; break; + case STBI__F_paeth_first: cur[k] = raw[k]; break; + } + } + + if (depth == 8) { + if (img_n != out_n) + cur[img_n] = 255; // first pixel + raw += img_n; + cur += out_n; + prior += out_n; + } else if (depth == 16) { + if (img_n != out_n) { + cur[filter_bytes] = 255; // first pixel top byte + cur[filter_bytes+1] = 255; // first pixel bottom byte + } + raw += filter_bytes; + cur += output_bytes; + prior += output_bytes; + } else { + raw += 1; + cur += 1; + prior += 1; + } + + // this is a little gross, so that we don't switch per-pixel or per-component + if (depth < 8 || img_n == out_n) { + int nk = (width - 1)*filter_bytes; + #define STBI__CASE(f) \ + case f: \ + for (k=0; k < nk; ++k) + switch (filter) { + // "none" filter turns into a memcpy here; make that explicit. + case STBI__F_none: memcpy(cur, raw, nk); break; + STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break; + STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; + STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break; + STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break; + STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break; + STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break; + } + #undef STBI__CASE + raw += nk; + } else { + STBI_ASSERT(img_n+1 == out_n); + #define STBI__CASE(f) \ + case f: \ + for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \ + for (k=0; k < filter_bytes; ++k) + switch (filter) { + STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break; + STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break; + STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; + STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break; + STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break; + STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break; + STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break; + } + #undef STBI__CASE + + // the loop above sets the high byte of the pixels' alpha, but for + // 16 bit png files we also need the low byte set. we'll do that here. + if (depth == 16) { + cur = a->out + stride*j; // start at the beginning of the row again + for (i=0; i < x; ++i,cur+=output_bytes) { + cur[filter_bytes+1] = 255; + } + } + } + } + + // we make a separate pass to expand bits to pixels; for performance, + // this could run two scanlines behind the above code, so it won't + // intefere with filtering but will still be in the cache. + if (depth < 8) { + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes; + // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit + // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop + stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range + + // note that the final byte might overshoot and write more data than desired. + // we can allocate enough data that this never writes out of memory, but it + // could also overwrite the next scanline. can it overwrite non-empty data + // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel. + // so we need to explicitly clamp the final ones + + if (depth == 4) { + for (k=x*img_n; k >= 2; k-=2, ++in) { + *cur++ = scale * ((*in >> 4) ); + *cur++ = scale * ((*in ) & 0x0f); + } + if (k > 0) *cur++ = scale * ((*in >> 4) ); + } else if (depth == 2) { + for (k=x*img_n; k >= 4; k-=4, ++in) { + *cur++ = scale * ((*in >> 6) ); + *cur++ = scale * ((*in >> 4) & 0x03); + *cur++ = scale * ((*in >> 2) & 0x03); + *cur++ = scale * ((*in ) & 0x03); + } + if (k > 0) *cur++ = scale * ((*in >> 6) ); + if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); + if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); + } else if (depth == 1) { + for (k=x*img_n; k >= 8; k-=8, ++in) { + *cur++ = scale * ((*in >> 7) ); + *cur++ = scale * ((*in >> 6) & 0x01); + *cur++ = scale * ((*in >> 5) & 0x01); + *cur++ = scale * ((*in >> 4) & 0x01); + *cur++ = scale * ((*in >> 3) & 0x01); + *cur++ = scale * ((*in >> 2) & 0x01); + *cur++ = scale * ((*in >> 1) & 0x01); + *cur++ = scale * ((*in ) & 0x01); + } + if (k > 0) *cur++ = scale * ((*in >> 7) ); + if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); + if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); + if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); + if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); + if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); + if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); + } + if (img_n != out_n) { + int q; + // insert alpha = 255 + cur = a->out + stride*j; + if (img_n == 1) { + for (q=x-1; q >= 0; --q) { + cur[q*2+1] = 255; + cur[q*2+0] = cur[q]; + } + } else { + STBI_ASSERT(img_n == 3); + for (q=x-1; q >= 0; --q) { + cur[q*4+3] = 255; + cur[q*4+2] = cur[q*3+2]; + cur[q*4+1] = cur[q*3+1]; + cur[q*4+0] = cur[q*3+0]; + } + } + } + } + } else if (depth == 16) { + // force the image data from big-endian to platform-native. + // this is done in a separate pass due to the decoding relying + // on the data being untouched, but could probably be done + // per-line during decode if care is taken. + stbi_uc *cur = a->out; + stbi__uint16 *cur16 = (stbi__uint16*)cur; + + for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) { + *cur16 = (cur[0] << 8) | cur[1]; + } + } + + return 1; +} + +static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) +{ + int bytes = (depth == 16 ? 2 : 1); + int out_bytes = out_n * bytes; + stbi_uc *final; + int p; + if (!interlaced) + return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); + + // de-interlacing + final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); + for (p=0; p < 7; ++p) { + int xorig[] = { 0,4,0,2,0,1,0 }; + int yorig[] = { 0,0,4,0,2,0,1 }; + int xspc[] = { 8,8,4,4,2,2,1 }; + int yspc[] = { 8,8,8,4,4,2,2 }; + int i,j,x,y; + // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 + x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; + y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; + if (x && y) { + stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; + if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { + STBI_FREE(final); + return 0; + } + for (j=0; j < y; ++j) { + for (i=0; i < x; ++i) { + int out_y = j*yspc[p]+yorig[p]; + int out_x = i*xspc[p]+xorig[p]; + memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes, + a->out + (j*x+i)*out_bytes, out_bytes); + } + } + STBI_FREE(a->out); + image_data += img_len; + image_data_len -= img_len; + } + } + a->out = final; + + return 1; +} + +static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + // compute color-based transparency, assuming we've + // already got 255 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i=0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 255); + p += 2; + } + } else { + for (i=0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi__uint16 *p = (stbi__uint16*) z->out; + + // compute color-based transparency, assuming we've + // already got 65535 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i = 0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 65535); + p += 2; + } + } else { + for (i = 0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) +{ + stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; + stbi_uc *p, *temp_out, *orig = a->out; + + p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0); + if (p == NULL) return stbi__err("outofmem", "Out of memory"); + + // between here and free(out) below, exitting would leak + temp_out = p; + + if (pal_img_n == 3) { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p += 3; + } + } else { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p[3] = palette[n+3]; + p += 4; + } + } + STBI_FREE(a->out); + a->out = temp_out; + + STBI_NOTUSED(len); + + return 1; +} + +static int stbi__unpremultiply_on_load = 0; +static int stbi__de_iphone_flag = 0; + +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load = flag_true_if_should_unpremultiply; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag = flag_true_if_should_convert; +} + +static void stbi__de_iphone(stbi__png *z) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + if (s->img_out_n == 3) { // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 3; + } + } else { + STBI_ASSERT(s->img_out_n == 4); + if (stbi__unpremultiply_on_load) { + // convert bgr to rgb and unpremultiply + for (i=0; i < pixel_count; ++i) { + stbi_uc a = p[3]; + stbi_uc t = p[0]; + if (a) { + p[0] = p[2] * 255 / a; + p[1] = p[1] * 255 / a; + p[2] = t * 255 / a; + } else { + p[0] = p[2]; + p[2] = t; + } + p += 4; + } + } else { + // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 4; + } + } + } +} + +#define STBI__PNG_TYPE(a,b,c,d) (((a) << 24) + ((b) << 16) + ((c) << 8) + (d)) + +static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) +{ + stbi_uc palette[1024], pal_img_n=0; + stbi_uc has_trans=0, tc[3]; + stbi__uint16 tc16[3]; + stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; + int first=1,k,interlace=0, color=0, is_iphone=0; + stbi__context *s = z->s; + + z->expanded = NULL; + z->idata = NULL; + z->out = NULL; + + if (!stbi__check_png_header(s)) return 0; + + if (scan == STBI__SCAN_type) return 1; + + for (;;) { + stbi__pngchunk c = stbi__get_chunk_header(s); + switch (c.type) { + case STBI__PNG_TYPE('C','g','B','I'): + is_iphone = 1; + stbi__skip(s, c.length); + break; + case STBI__PNG_TYPE('I','H','D','R'): { + int comp,filter; + if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); + first = 0; + if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); + s->img_x = stbi__get32be(s); if (s->img_x > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); + s->img_y = stbi__get32be(s); if (s->img_y > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); + z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only"); + color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); + comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); + filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); + interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); + if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); + if (!pal_img_n) { + s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); + if (scan == STBI__SCAN_header) return 1; + } else { + // if paletted, then pal_n is our final components, and + // img_n is # components to decompress/filter. + s->img_n = 1; + if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); + // if SCAN_header, have to scan to see if we have a tRNS + } + break; + } + + case STBI__PNG_TYPE('P','L','T','E'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); + pal_len = c.length / 3; + if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); + for (i=0; i < pal_len; ++i) { + palette[i*4+0] = stbi__get8(s); + palette[i*4+1] = stbi__get8(s); + palette[i*4+2] = stbi__get8(s); + palette[i*4+3] = 255; + } + break; + } + + case STBI__PNG_TYPE('t','R','N','S'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); + if (pal_img_n) { + if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } + if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); + if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); + pal_img_n = 4; + for (i=0; i < c.length; ++i) + palette[i*4+3] = stbi__get8(s); + } else { + if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); + if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); + has_trans = 1; + if (z->depth == 16) { + for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is + } else { + for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger + } + } + break; + } + + case STBI__PNG_TYPE('I','D','A','T'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); + if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; } + if ((int)(ioff + c.length) < (int)ioff) return 0; + if (ioff + c.length > idata_limit) { + stbi__uint32 idata_limit_old = idata_limit; + stbi_uc *p; + if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; + while (ioff + c.length > idata_limit) + idata_limit *= 2; + STBI_NOTUSED(idata_limit_old); + p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); + z->idata = p; + } + if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); + ioff += c.length; + break; + } + + case STBI__PNG_TYPE('I','E','N','D'): { + stbi__uint32 raw_len, bpl; + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (scan != STBI__SCAN_load) return 1; + if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); + // initial guess for decoded data size to avoid unnecessary reallocs + bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component + raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; + z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); + if (z->expanded == NULL) return 0; // zlib should set error + STBI_FREE(z->idata); z->idata = NULL; + if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) + s->img_out_n = s->img_n+1; + else + s->img_out_n = s->img_n; + if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; + if (has_trans) { + if (z->depth == 16) { + if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; + } else { + if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; + } + } + if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) + stbi__de_iphone(z); + if (pal_img_n) { + // pal_img_n == 3 or 4 + s->img_n = pal_img_n; // record the actual colors we had + s->img_out_n = pal_img_n; + if (req_comp >= 3) s->img_out_n = req_comp; + if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) + return 0; + } + STBI_FREE(z->expanded); z->expanded = NULL; + return 1; + } + + default: + // if critical, fail + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if ((c.type & (1 << 29)) == 0) { + #ifndef STBI_NO_FAILURE_STRINGS + // not threadsafe + static char invalid_chunk[] = "XXXX PNG chunk not known"; + invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); + invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); + invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); + invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); + #endif + return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); + } + stbi__skip(s, c.length); + break; + } + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + } +} + +static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri) +{ + void *result=NULL; + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { + if (p->depth < 8) + ri->bits_per_channel = 8; + else + ri->bits_per_channel = p->depth; + result = p->out; + p->out = NULL; + if (req_comp && req_comp != p->s->img_out_n) { + if (ri->bits_per_channel == 8) + result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + else + result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + p->s->img_out_n = req_comp; + if (result == NULL) return result; + } + *x = p->s->img_x; + *y = p->s->img_y; + if (n) *n = p->s->img_n; + } + STBI_FREE(p->out); p->out = NULL; + STBI_FREE(p->expanded); p->expanded = NULL; + STBI_FREE(p->idata); p->idata = NULL; + + return result; +} + +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi__png p; + p.s = s; + return stbi__do_png(&p, x,y,comp,req_comp, ri); +} + +static int stbi__png_test(stbi__context *s) +{ + int r; + r = stbi__check_png_header(s); + stbi__rewind(s); + return r; +} + +static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) +{ + if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { + stbi__rewind( p->s ); + return 0; + } + if (x) *x = p->s->img_x; + if (y) *y = p->s->img_y; + if (comp) *comp = p->s->img_n; + return 1; +} + +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__png p; + p.s = s; + return stbi__png_info_raw(&p, x, y, comp); +} +#endif + +// Microsoft/Windows BMP image + +#ifndef STBI_NO_BMP +static int stbi__bmp_test_raw(stbi__context *s) +{ + int r; + int sz; + if (stbi__get8(s) != 'B') return 0; + if (stbi__get8(s) != 'M') return 0; + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + stbi__get32le(s); // discard data offset + sz = stbi__get32le(s); + r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); + return r; +} + +static int stbi__bmp_test(stbi__context *s) +{ + int r = stbi__bmp_test_raw(s); + stbi__rewind(s); + return r; +} + + +// returns 0..31 for the highest set bit +static int stbi__high_bit(unsigned int z) +{ + int n=0; + if (z == 0) return -1; + if (z >= 0x10000) n += 16, z >>= 16; + if (z >= 0x00100) n += 8, z >>= 8; + if (z >= 0x00010) n += 4, z >>= 4; + if (z >= 0x00004) n += 2, z >>= 2; + if (z >= 0x00002) n += 1, z >>= 1; + return n; +} + +static int stbi__bitcount(unsigned int a) +{ + a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 + a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits + a = (a + (a >> 8)); // max 16 per 8 bits + a = (a + (a >> 16)); // max 32 per 8 bits + return a & 0xff; +} + +static int stbi__shiftsigned(int v, int shift, int bits) +{ + int result; + int z=0; + + if (shift < 0) v <<= -shift; + else v >>= shift; + result = v; + + z = bits; + while (z < 8) { + result += v >> z; + z += bits; + } + return result; +} + +typedef struct +{ + int bpp, offset, hsz; + unsigned int mr,mg,mb,ma, all_a; +} stbi__bmp_data; + +static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) +{ + int hsz; + if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + info->offset = stbi__get32le(s); + info->hsz = hsz = stbi__get32le(s); + info->mr = info->mg = info->mb = info->ma = 0; + + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); + if (hsz == 12) { + s->img_x = stbi__get16le(s); + s->img_y = stbi__get16le(s); + } else { + s->img_x = stbi__get32le(s); + s->img_y = stbi__get32le(s); + } + if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); + info->bpp = stbi__get16le(s); + if (info->bpp == 1) return stbi__errpuc("monochrome", "BMP type not supported: 1-bit"); + if (hsz != 12) { + int compress = stbi__get32le(s); + if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); + stbi__get32le(s); // discard sizeof + stbi__get32le(s); // discard hres + stbi__get32le(s); // discard vres + stbi__get32le(s); // discard colorsused + stbi__get32le(s); // discard max important + if (hsz == 40 || hsz == 56) { + if (hsz == 56) { + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + } + if (info->bpp == 16 || info->bpp == 32) { + if (compress == 0) { + if (info->bpp == 32) { + info->mr = 0xffu << 16; + info->mg = 0xffu << 8; + info->mb = 0xffu << 0; + info->ma = 0xffu << 24; + info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 + } else { + info->mr = 31u << 10; + info->mg = 31u << 5; + info->mb = 31u << 0; + } + } else if (compress == 3) { + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + // not documented, but generated by photoshop and handled by mspaint + if (info->mr == info->mg && info->mg == info->mb) { + // ?!?!? + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else { + int i; + if (hsz != 108 && hsz != 124) + return stbi__errpuc("bad BMP", "bad BMP"); + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->ma = stbi__get32le(s); + stbi__get32le(s); // discard color space + for (i=0; i < 12; ++i) + stbi__get32le(s); // discard color space parameters + if (hsz == 124) { + stbi__get32le(s); // discard rendering intent + stbi__get32le(s); // discard offset of profile data + stbi__get32le(s); // discard size of profile data + stbi__get32le(s); // discard reserved + } + } + } + return (void *) 1; +} + + +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + unsigned int mr=0,mg=0,mb=0,ma=0, all_a; + stbi_uc pal[256][4]; + int psize=0,i,j,width; + int flip_vertically, pad, target; + stbi__bmp_data info; + STBI_NOTUSED(ri); + + info.all_a = 255; + if (stbi__bmp_parse_header(s, &info) == NULL) + return NULL; // error code already set + + flip_vertically = ((int) s->img_y) > 0; + s->img_y = abs((int) s->img_y); + + mr = info.mr; + mg = info.mg; + mb = info.mb; + ma = info.ma; + all_a = info.all_a; + + if (info.hsz == 12) { + if (info.bpp < 24) + psize = (info.offset - 14 - 24) / 3; + } else { + if (info.bpp < 16) + psize = (info.offset - 14 - info.hsz) >> 2; + } + + s->img_n = ma ? 4 : 3; + if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 + target = req_comp; + else + target = s->img_n; // if they want monochrome, we'll post-convert + + // sanity-check size + if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0)) + return stbi__errpuc("too large", "Corrupt BMP"); + + out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (info.bpp < 16) { + int z=0; + if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } + for (i=0; i < psize; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + if (info.hsz != 12) stbi__get8(s); + pal[i][3] = 255; + } + stbi__skip(s, info.offset - 14 - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); + if (info.bpp == 4) width = (s->img_x + 1) >> 1; + else if (info.bpp == 8) width = s->img_x; + else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } + pad = (-width)&3; + for (j=0; j < (int) s->img_y; ++j) { + for (i=0; i < (int) s->img_x; i += 2) { + int v=stbi__get8(s),v2=0; + if (info.bpp == 4) { + v2 = v & 15; + v >>= 4; + } + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + v = (info.bpp == 8) ? stbi__get8(s) : v2; + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + } + stbi__skip(s, pad); + } + } else { + int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; + int z = 0; + int easy=0; + stbi__skip(s, info.offset - 14 - info.hsz); + if (info.bpp == 24) width = 3 * s->img_x; + else if (info.bpp == 16) width = 2*s->img_x; + else /* bpp = 32 and pad = 0 */ width=0; + pad = (-width) & 3; + if (info.bpp == 24) { + easy = 1; + } else if (info.bpp == 32) { + if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) + easy = 2; + } + if (!easy) { + if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + // right shift amt to put high bit in position #7 + rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); + gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); + bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); + ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); + } + for (j=0; j < (int) s->img_y; ++j) { + if (easy) { + for (i=0; i < (int) s->img_x; ++i) { + unsigned char a; + out[z+2] = stbi__get8(s); + out[z+1] = stbi__get8(s); + out[z+0] = stbi__get8(s); + z += 3; + a = (easy == 2 ? stbi__get8(s) : 255); + all_a |= a; + if (target == 4) out[z++] = a; + } + } else { + int bpp = info.bpp; + for (i=0; i < (int) s->img_x; ++i) { + stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); + int a; + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); + a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); + all_a |= a; + if (target == 4) out[z++] = STBI__BYTECAST(a); + } + } + stbi__skip(s, pad); + } + } + + // if alpha channel is all 0s, replace with all 255s + if (target == 4 && all_a == 0) + for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) + out[i] = 255; + + if (flip_vertically) { + stbi_uc t; + for (j=0; j < (int) s->img_y>>1; ++j) { + stbi_uc *p1 = out + j *s->img_x*target; + stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; + for (i=0; i < (int) s->img_x*target; ++i) { + t = p1[i], p1[i] = p2[i], p2[i] = t; + } + } + } + + if (req_comp && req_comp != target) { + out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + return out; +} +#endif + +// Targa Truevision - TGA +// by Jonathan Dummer +#ifndef STBI_NO_TGA +// returns STBI_rgb or whatever, 0 on error +static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) +{ + // only RGB or RGBA (incl. 16bit) or grey allowed + if(is_rgb16) *is_rgb16 = 0; + switch(bits_per_pixel) { + case 8: return STBI_grey; + case 16: if(is_grey) return STBI_grey_alpha; + // else: fall-through + case 15: if(is_rgb16) *is_rgb16 = 1; + return STBI_rgb; + case 24: // fall-through + case 32: return bits_per_pixel/8; + default: return 0; + } +} + +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) +{ + int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; + int sz, tga_colormap_type; + stbi__get8(s); // discard Offset + tga_colormap_type = stbi__get8(s); // colormap type + if( tga_colormap_type > 1 ) { + stbi__rewind(s); + return 0; // only RGB or indexed allowed + } + tga_image_type = stbi__get8(s); // image type + if ( tga_colormap_type == 1 ) { // colormapped (paletted) image + if (tga_image_type != 1 && tga_image_type != 9) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip image x and y origin + tga_colormap_bpp = sz; + } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE + if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { + stbi__rewind(s); + return 0; // only RGB or grey allowed, +/- RLE + } + stbi__skip(s,9); // skip colormap specification and image x/y origin + tga_colormap_bpp = 0; + } + tga_w = stbi__get16le(s); + if( tga_w < 1 ) { + stbi__rewind(s); + return 0; // test width + } + tga_h = stbi__get16le(s); + if( tga_h < 1 ) { + stbi__rewind(s); + return 0; // test height + } + tga_bits_per_pixel = stbi__get8(s); // bits per pixel + stbi__get8(s); // ignore alpha bits + if (tga_colormap_bpp != 0) { + if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { + // when using a colormap, tga_bits_per_pixel is the size of the indexes + // I don't think anything but 8 or 16bit indexes makes sense + stbi__rewind(s); + return 0; + } + tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); + } else { + tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); + } + if(!tga_comp) { + stbi__rewind(s); + return 0; + } + if (x) *x = tga_w; + if (y) *y = tga_h; + if (comp) *comp = tga_comp; + return 1; // seems to have passed everything +} + +static int stbi__tga_test(stbi__context *s) +{ + int res = 0; + int sz, tga_color_type; + stbi__get8(s); // discard Offset + tga_color_type = stbi__get8(s); // color type + if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed + sz = stbi__get8(s); // image type + if ( tga_color_type == 1 ) { // colormapped (paletted) image + if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + stbi__skip(s,4); // skip image x and y origin + } else { // "normal" image w/o colormap + if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE + stbi__skip(s,9); // skip colormap specification and image x/y origin + } + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height + sz = stbi__get8(s); // bits per pixel + if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + + res = 1; // if we got this far, everything's good and we can return 1 instead of 0 + +errorEnd: + stbi__rewind(s); + return res; +} + +// read 16bit value and convert to 24bit RGB +static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) +{ + stbi__uint16 px = (stbi__uint16)stbi__get16le(s); + stbi__uint16 fiveBitMask = 31; + // we have 3 channels with 5bits each + int r = (px >> 10) & fiveBitMask; + int g = (px >> 5) & fiveBitMask; + int b = px & fiveBitMask; + // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later + out[0] = (stbi_uc)((r * 255)/31); + out[1] = (stbi_uc)((g * 255)/31); + out[2] = (stbi_uc)((b * 255)/31); + + // some people claim that the most significant bit might be used for alpha + // (possibly if an alpha-bit is set in the "image descriptor byte") + // but that only made 16bit test images completely translucent.. + // so let's treat all 15 and 16bit TGAs as RGB with no alpha. +} + +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + // read in the TGA header stuff + int tga_offset = stbi__get8(s); + int tga_indexed = stbi__get8(s); + int tga_image_type = stbi__get8(s); + int tga_is_RLE = 0; + int tga_palette_start = stbi__get16le(s); + int tga_palette_len = stbi__get16le(s); + int tga_palette_bits = stbi__get8(s); + int tga_x_origin = stbi__get16le(s); + int tga_y_origin = stbi__get16le(s); + int tga_width = stbi__get16le(s); + int tga_height = stbi__get16le(s); + int tga_bits_per_pixel = stbi__get8(s); + int tga_comp, tga_rgb16=0; + int tga_inverted = stbi__get8(s); + // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) + // image data + unsigned char *tga_data; + unsigned char *tga_palette = NULL; + int i, j; + unsigned char raw_data[4] = {0}; + int RLE_count = 0; + int RLE_repeating = 0; + int read_next_pixel = 1; + STBI_NOTUSED(ri); + + // do a tiny bit of precessing + if ( tga_image_type >= 8 ) + { + tga_image_type -= 8; + tga_is_RLE = 1; + } + tga_inverted = 1 - ((tga_inverted >> 5) & 1); + + // If I'm paletted, then I'll use the number of bits from the palette + if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); + else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); + + if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency + return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); + + // tga info + *x = tga_width; + *y = tga_height; + if (comp) *comp = tga_comp; + + if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0)) + return stbi__errpuc("too large", "Corrupt TGA"); + + tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0); + if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); + + // skip to the data's starting position (offset usually = 0) + stbi__skip(s, tga_offset ); + + if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { + for (i=0; i < tga_height; ++i) { + int row = tga_inverted ? tga_height -i - 1 : i; + stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; + stbi__getn(s, tga_row, tga_width * tga_comp); + } + } else { + // do I need to load a palette? + if ( tga_indexed) + { + // any data to skip? (offset usually = 0) + stbi__skip(s, tga_palette_start ); + // load the palette + tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0); + if (!tga_palette) { + STBI_FREE(tga_data); + return stbi__errpuc("outofmem", "Out of memory"); + } + if (tga_rgb16) { + stbi_uc *pal_entry = tga_palette; + STBI_ASSERT(tga_comp == STBI_rgb); + for (i=0; i < tga_palette_len; ++i) { + stbi__tga_read_rgb16(s, pal_entry); + pal_entry += tga_comp; + } + } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { + STBI_FREE(tga_data); + STBI_FREE(tga_palette); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + } + // load the data + for (i=0; i < tga_width * tga_height; ++i) + { + // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? + if ( tga_is_RLE ) + { + if ( RLE_count == 0 ) + { + // yep, get the next byte as a RLE command + int RLE_cmd = stbi__get8(s); + RLE_count = 1 + (RLE_cmd & 127); + RLE_repeating = RLE_cmd >> 7; + read_next_pixel = 1; + } else if ( !RLE_repeating ) + { + read_next_pixel = 1; + } + } else + { + read_next_pixel = 1; + } + // OK, if I need to read a pixel, do it now + if ( read_next_pixel ) + { + // load however much data we did have + if ( tga_indexed ) + { + // read in index, then perform the lookup + int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); + if ( pal_idx >= tga_palette_len ) { + // invalid index + pal_idx = 0; + } + pal_idx *= tga_comp; + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = tga_palette[pal_idx+j]; + } + } else if(tga_rgb16) { + STBI_ASSERT(tga_comp == STBI_rgb); + stbi__tga_read_rgb16(s, raw_data); + } else { + // read in the data raw + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = stbi__get8(s); + } + } + // clear the reading flag for the next pixel + read_next_pixel = 0; + } // end of reading a pixel + + // copy data + for (j = 0; j < tga_comp; ++j) + tga_data[i*tga_comp+j] = raw_data[j]; + + // in case we're in RLE mode, keep counting down + --RLE_count; + } + // do I need to invert the image? + if ( tga_inverted ) + { + for (j = 0; j*2 < tga_height; ++j) + { + int index1 = j * tga_width * tga_comp; + int index2 = (tga_height - 1 - j) * tga_width * tga_comp; + for (i = tga_width * tga_comp; i > 0; --i) + { + unsigned char temp = tga_data[index1]; + tga_data[index1] = tga_data[index2]; + tga_data[index2] = temp; + ++index1; + ++index2; + } + } + } + // clear my palette, if I had one + if ( tga_palette != NULL ) + { + STBI_FREE( tga_palette ); + } + } + + // swap RGB - if the source data was RGB16, it already is in the right order + if (tga_comp >= 3 && !tga_rgb16) + { + unsigned char* tga_pixel = tga_data; + for (i=0; i < tga_width * tga_height; ++i) + { + unsigned char temp = tga_pixel[0]; + tga_pixel[0] = tga_pixel[2]; + tga_pixel[2] = temp; + tga_pixel += tga_comp; + } + } + + // convert to target component count + if (req_comp && req_comp != tga_comp) + tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); + + // the things I do to get rid of an error message, and yet keep + // Microsoft's C compilers happy... [8^( + tga_palette_start = tga_palette_len = tga_palette_bits = + tga_x_origin = tga_y_origin = 0; + // OK, done + return tga_data; +} +#endif + +// ************************************************************************************************* +// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s) +{ + int r = (stbi__get32be(s) == 0x38425053); + stbi__rewind(s); + return r; +} + +static int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount) +{ + int count, nleft, len; + + count = 0; + while ((nleft = pixelCount - count) > 0) { + len = stbi__get8(s); + if (len == 128) { + // No-op. + } else if (len < 128) { + // Copy next len+1 bytes literally. + len++; + if (len > nleft) return 0; // corrupt data + count += len; + while (len) { + *p = stbi__get8(s); + p += 4; + len--; + } + } else if (len > 128) { + stbi_uc val; + // Next -len+1 bytes in the dest are replicated from next source byte. + // (Interpret len as a negative 8-bit int.) + len = 257 - len; + if (len > nleft) return 0; // corrupt data + val = stbi__get8(s); + count += len; + while (len) { + *p = val; + p += 4; + len--; + } + } + } + + return 1; +} + +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + int pixelCount; + int channelCount, compression; + int channel, i; + int bitdepth; + int w,h; + stbi_uc *out; + STBI_NOTUSED(ri); + + // Check identifier + if (stbi__get32be(s) != 0x38425053) // "8BPS" + return stbi__errpuc("not PSD", "Corrupt PSD image"); + + // Check file type version. + if (stbi__get16be(s) != 1) + return stbi__errpuc("wrong version", "Unsupported version of PSD image"); + + // Skip 6 reserved bytes. + stbi__skip(s, 6 ); + + // Read the number of channels (R, G, B, A, etc). + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) + return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); + + // Read the rows and columns of the image. + h = stbi__get32be(s); + w = stbi__get32be(s); + + // Make sure the depth is 8 bits. + bitdepth = stbi__get16be(s); + if (bitdepth != 8 && bitdepth != 16) + return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); + + // Make sure the color mode is RGB. + // Valid options are: + // 0: Bitmap + // 1: Grayscale + // 2: Indexed color + // 3: RGB color + // 4: CMYK color + // 7: Multichannel + // 8: Duotone + // 9: Lab color + if (stbi__get16be(s) != 3) + return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); + + // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) + stbi__skip(s,stbi__get32be(s) ); + + // Skip the image resources. (resolution, pen tool paths, etc) + stbi__skip(s, stbi__get32be(s) ); + + // Skip the reserved data. + stbi__skip(s, stbi__get32be(s) ); + + // Find out if the data is compressed. + // Known values: + // 0: no compression + // 1: RLE compressed + compression = stbi__get16be(s); + if (compression > 1) + return stbi__errpuc("bad compression", "PSD has an unknown compression format"); + + // Check size + if (!stbi__mad3sizes_valid(4, w, h, 0)) + return stbi__errpuc("too large", "Corrupt PSD"); + + // Create the destination image. + + if (!compression && bitdepth == 16 && bpc == 16) { + out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0); + ri->bits_per_channel = 16; + } else + out = (stbi_uc *) stbi__malloc(4 * w*h); + + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + pixelCount = w*h; + + // Initialize the data to zero. + //memset( out, 0, pixelCount * 4 ); + + // Finally, the image data. + if (compression) { + // RLE as used by .PSD and .TIFF + // Loop until you get the number of unpacked bytes you are expecting: + // Read the next source byte into n. + // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. + // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. + // Else if n is 128, noop. + // Endloop + + // The RLE-compressed data is preceeded by a 2-byte data count for each row in the data, + // which we're going to just skip. + stbi__skip(s, h * channelCount * 2 ); + + // Read the RLE data by channel. + for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out+channel; + if (channel >= channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++, p += 4) + *p = (channel == 3 ? 255 : 0); + } else { + // Read the RLE data. + if (!stbi__psd_decode_rle(s, p, pixelCount)) { + STBI_FREE(out); + return stbi__errpuc("corrupt", "bad RLE data"); + } + } + } + + } else { + // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) + // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image. + + // Read the data by channel. + for (channel = 0; channel < 4; channel++) { + if (channel >= channelCount) { + // Fill this channel with default data. + if (bitdepth == 16 && bpc == 16) { + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + stbi__uint16 val = channel == 3 ? 65535 : 0; + for (i = 0; i < pixelCount; i++, q += 4) + *q = val; + } else { + stbi_uc *p = out+channel; + stbi_uc val = channel == 3 ? 255 : 0; + for (i = 0; i < pixelCount; i++, p += 4) + *p = val; + } + } else { + if (ri->bits_per_channel == 16) { // output bpc + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + for (i = 0; i < pixelCount; i++, q += 4) + *q = (stbi__uint16) stbi__get16be(s); + } else { + stbi_uc *p = out+channel; + if (bitdepth == 16) { // input bpc + for (i = 0; i < pixelCount; i++, p += 4) + *p = (stbi_uc) (stbi__get16be(s) >> 8); + } else { + for (i = 0; i < pixelCount; i++, p += 4) + *p = stbi__get8(s); + } + } + } + } + } + + // remove weird white matte from PSD + if (channelCount >= 4) { + if (ri->bits_per_channel == 16) { + for (i=0; i < w*h; ++i) { + stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i; + if (pixel[3] != 0 && pixel[3] != 65535) { + float a = pixel[3] / 65535.0f; + float ra = 1.0f / a; + float inv_a = 65535.0f * (1 - ra); + pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a); + pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a); + pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a); + } + } + } else { + for (i=0; i < w*h; ++i) { + unsigned char *pixel = out + 4*i; + if (pixel[3] != 0 && pixel[3] != 255) { + float a = pixel[3] / 255.0f; + float ra = 1.0f / a; + float inv_a = 255.0f * (1 - ra); + pixel[0] = (unsigned char) (pixel[0]*ra + inv_a); + pixel[1] = (unsigned char) (pixel[1]*ra + inv_a); + pixel[2] = (unsigned char) (pixel[2]*ra + inv_a); + } + } + } + } + + // convert to desired output format + if (req_comp && req_comp != 4) { + if (ri->bits_per_channel == 16) + out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h); + else + out = stbi__convert_format(out, 4, req_comp, w, h); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + if (comp) *comp = 4; + *y = h; + *x = w; + + return out; +} +#endif + +// ************************************************************************************************* +// Softimage PIC loader +// by Tom Seddon +// +// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format +// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ + +#ifndef STBI_NO_PIC +static int stbi__pic_is4(stbi__context *s,const char *str) +{ + int i; + for (i=0; i<4; ++i) + if (stbi__get8(s) != (stbi_uc)str[i]) + return 0; + + return 1; +} + +static int stbi__pic_test_core(stbi__context *s) +{ + int i; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) + return 0; + + for(i=0;i<84;++i) + stbi__get8(s); + + if (!stbi__pic_is4(s,"PICT")) + return 0; + + return 1; +} + +typedef struct +{ + stbi_uc size,type,channel; +} stbi__pic_packet; + +static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) +{ + int mask=0x80, i; + + for (i=0; i<4; ++i, mask>>=1) { + if (channel & mask) { + if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); + dest[i]=stbi__get8(s); + } + } + + return dest; +} + +static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) +{ + int mask=0x80,i; + + for (i=0;i<4; ++i, mask>>=1) + if (channel&mask) + dest[i]=src[i]; +} + +static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) +{ + int act_comp=0,num_packets=0,y,chained; + stbi__pic_packet packets[10]; + + // this will (should...) cater for even some bizarre stuff like having data + // for the same channel in multiple packets. + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return stbi__errpuc("bad format","too many packets"); + + packet = &packets[num_packets++]; + + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + + act_comp |= packet->channel; + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); + if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? + + for(y=0; ytype) { + default: + return stbi__errpuc("bad format","packet has bad compression type"); + + case 0: {//uncompressed + int x; + + for(x=0;xchannel,dest)) + return 0; + break; + } + + case 1://Pure RLE + { + int left=width, i; + + while (left>0) { + stbi_uc count,value[4]; + + count=stbi__get8(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); + + if (count > left) + count = (stbi_uc) left; + + if (!stbi__readval(s,packet->channel,value)) return 0; + + for(i=0; ichannel,dest,value); + left -= count; + } + } + break; + + case 2: {//Mixed RLE + int left=width; + while (left>0) { + int count = stbi__get8(s), i; + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); + + if (count >= 128) { // Repeated + stbi_uc value[4]; + + if (count==128) + count = stbi__get16be(s); + else + count -= 127; + if (count > left) + return stbi__errpuc("bad file","scanline overrun"); + + if (!stbi__readval(s,packet->channel,value)) + return 0; + + for(i=0;ichannel,dest,value); + } else { // Raw + ++count; + if (count>left) return stbi__errpuc("bad file","scanline overrun"); + + for(i=0;ichannel,dest)) + return 0; + } + left-=count; + } + break; + } + } + } + } + + return result; +} + +static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri) +{ + stbi_uc *result; + int i, x,y, internal_comp; + STBI_NOTUSED(ri); + + if (!comp) comp = &internal_comp; + + for (i=0; i<92; ++i) + stbi__get8(s); + + x = stbi__get16be(s); + y = stbi__get16be(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); + if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode"); + + stbi__get32be(s); //skip `ratio' + stbi__get16be(s); //skip `fields' + stbi__get16be(s); //skip `pad' + + // intermediate buffer is RGBA + result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0); + memset(result, 0xff, x*y*4); + + if (!stbi__pic_load_core(s,x,y,comp, result)) { + STBI_FREE(result); + result=0; + } + *px = x; + *py = y; + if (req_comp == 0) req_comp = *comp; + result=stbi__convert_format(result,4,req_comp,x,y); + + return result; +} + +static int stbi__pic_test(stbi__context *s) +{ + int r = stbi__pic_test_core(s); + stbi__rewind(s); + return r; +} +#endif + +// ************************************************************************************************* +// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb + +#ifndef STBI_NO_GIF +typedef struct +{ + stbi__int16 prefix; + stbi_uc first; + stbi_uc suffix; +} stbi__gif_lzw; + +typedef struct +{ + int w,h; + stbi_uc *out, *old_out; // output buffer (always 4 components) + int flags, bgindex, ratio, transparent, eflags, delay; + stbi_uc pal[256][4]; + stbi_uc lpal[256][4]; + stbi__gif_lzw codes[4096]; + stbi_uc *color_table; + int parse, step; + int lflags; + int start_x, start_y; + int max_x, max_y; + int cur_x, cur_y; + int line_size; +} stbi__gif; + +static int stbi__gif_test_raw(stbi__context *s) +{ + int sz; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; + sz = stbi__get8(s); + if (sz != '9' && sz != '7') return 0; + if (stbi__get8(s) != 'a') return 0; + return 1; +} + +static int stbi__gif_test(stbi__context *s) +{ + int r = stbi__gif_test_raw(s); + stbi__rewind(s); + return r; +} + +static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) +{ + int i; + for (i=0; i < num_entries; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + pal[i][3] = transp == i ? 0 : 255; + } +} + +static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) +{ + stbi_uc version; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') + return stbi__err("not GIF", "Corrupt GIF"); + + version = stbi__get8(s); + if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); + if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); + + stbi__g_failure_reason = ""; + g->w = stbi__get16le(s); + g->h = stbi__get16le(s); + g->flags = stbi__get8(s); + g->bgindex = stbi__get8(s); + g->ratio = stbi__get8(s); + g->transparent = -1; + + if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments + + if (is_info) return 1; + + if (g->flags & 0x80) + stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); + + return 1; +} + +static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); + if (!stbi__gif_header(s, g, comp, 1)) { + STBI_FREE(g); + stbi__rewind( s ); + return 0; + } + if (x) *x = g->w; + if (y) *y = g->h; + STBI_FREE(g); + return 1; +} + +static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) +{ + stbi_uc *p, *c; + + // recurse to decode the prefixes, since the linked-list is backwards, + // and working backwards through an interleaved image would be nasty + if (g->codes[code].prefix >= 0) + stbi__out_gif_code(g, g->codes[code].prefix); + + if (g->cur_y >= g->max_y) return; + + p = &g->out[g->cur_x + g->cur_y]; + c = &g->color_table[g->codes[code].suffix * 4]; + + if (c[3] >= 128) { + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = c[3]; + } + g->cur_x += 4; + + if (g->cur_x >= g->max_x) { + g->cur_x = g->start_x; + g->cur_y += g->step; + + while (g->cur_y >= g->max_y && g->parse > 0) { + g->step = (1 << g->parse) * g->line_size; + g->cur_y = g->start_y + (g->step >> 1); + --g->parse; + } + } +} + +static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) +{ + stbi_uc lzw_cs; + stbi__int32 len, init_code; + stbi__uint32 first; + stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; + stbi__gif_lzw *p; + + lzw_cs = stbi__get8(s); + if (lzw_cs > 12) return NULL; + clear = 1 << lzw_cs; + first = 1; + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + bits = 0; + valid_bits = 0; + for (init_code = 0; init_code < clear; init_code++) { + g->codes[init_code].prefix = -1; + g->codes[init_code].first = (stbi_uc) init_code; + g->codes[init_code].suffix = (stbi_uc) init_code; + } + + // support no starting clear code + avail = clear+2; + oldcode = -1; + + len = 0; + for(;;) { + if (valid_bits < codesize) { + if (len == 0) { + len = stbi__get8(s); // start new block + if (len == 0) + return g->out; + } + --len; + bits |= (stbi__int32) stbi__get8(s) << valid_bits; + valid_bits += 8; + } else { + stbi__int32 code = bits & codemask; + bits >>= codesize; + valid_bits -= codesize; + // @OPTIMIZE: is there some way we can accelerate the non-clear path? + if (code == clear) { // clear code + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + avail = clear + 2; + oldcode = -1; + first = 0; + } else if (code == clear + 1) { // end of stream code + stbi__skip(s, len); + while ((len = stbi__get8(s)) > 0) + stbi__skip(s,len); + return g->out; + } else if (code <= avail) { + if (first) return stbi__errpuc("no clear code", "Corrupt GIF"); + + if (oldcode >= 0) { + p = &g->codes[avail++]; + if (avail > 4096) return stbi__errpuc("too many codes", "Corrupt GIF"); + p->prefix = (stbi__int16) oldcode; + p->first = g->codes[oldcode].first; + p->suffix = (code == avail) ? p->first : g->codes[code].first; + } else if (code == avail) + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + + stbi__out_gif_code(g, (stbi__uint16) code); + + if ((avail & codemask) == 0 && avail <= 0x0FFF) { + codesize++; + codemask = (1 << codesize) - 1; + } + + oldcode = code; + } else { + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + } + } + } +} + +static void stbi__fill_gif_background(stbi__gif *g, int x0, int y0, int x1, int y1) +{ + int x, y; + stbi_uc *c = g->pal[g->bgindex]; + for (y = y0; y < y1; y += 4 * g->w) { + for (x = x0; x < x1; x += 4) { + stbi_uc *p = &g->out[y + x]; + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = 0; + } + } +} + +// this function is designed to support animated gifs, although stb_image doesn't support it +static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp) +{ + int i; + stbi_uc *prev_out = 0; + + if (g->out == 0 && !stbi__gif_header(s, g, comp,0)) + return 0; // stbi__g_failure_reason set by stbi__gif_header + + if (!stbi__mad3sizes_valid(g->w, g->h, 4, 0)) + return stbi__errpuc("too large", "GIF too large"); + + prev_out = g->out; + g->out = (stbi_uc *) stbi__malloc_mad3(4, g->w, g->h, 0); + if (g->out == 0) return stbi__errpuc("outofmem", "Out of memory"); + + switch ((g->eflags & 0x1C) >> 2) { + case 0: // unspecified (also always used on 1st frame) + stbi__fill_gif_background(g, 0, 0, 4 * g->w, 4 * g->w * g->h); + break; + case 1: // do not dispose + if (prev_out) memcpy(g->out, prev_out, 4 * g->w * g->h); + g->old_out = prev_out; + break; + case 2: // dispose to background + if (prev_out) memcpy(g->out, prev_out, 4 * g->w * g->h); + stbi__fill_gif_background(g, g->start_x, g->start_y, g->max_x, g->max_y); + break; + case 3: // dispose to previous + if (g->old_out) { + for (i = g->start_y; i < g->max_y; i += 4 * g->w) + memcpy(&g->out[i + g->start_x], &g->old_out[i + g->start_x], g->max_x - g->start_x); + } + break; + } + + for (;;) { + switch (stbi__get8(s)) { + case 0x2C: /* Image Descriptor */ + { + int prev_trans = -1; + stbi__int32 x, y, w, h; + stbi_uc *o; + + x = stbi__get16le(s); + y = stbi__get16le(s); + w = stbi__get16le(s); + h = stbi__get16le(s); + if (((x + w) > (g->w)) || ((y + h) > (g->h))) + return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); + + g->line_size = g->w * 4; + g->start_x = x * 4; + g->start_y = y * g->line_size; + g->max_x = g->start_x + w * 4; + g->max_y = g->start_y + h * g->line_size; + g->cur_x = g->start_x; + g->cur_y = g->start_y; + + g->lflags = stbi__get8(s); + + if (g->lflags & 0x40) { + g->step = 8 * g->line_size; // first interlaced spacing + g->parse = 3; + } else { + g->step = g->line_size; + g->parse = 0; + } + + if (g->lflags & 0x80) { + stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); + g->color_table = (stbi_uc *) g->lpal; + } else if (g->flags & 0x80) { + if (g->transparent >= 0 && (g->eflags & 0x01)) { + prev_trans = g->pal[g->transparent][3]; + g->pal[g->transparent][3] = 0; + } + g->color_table = (stbi_uc *) g->pal; + } else + return stbi__errpuc("missing color table", "Corrupt GIF"); + + o = stbi__process_gif_raster(s, g); + if (o == NULL) return NULL; + + if (prev_trans != -1) + g->pal[g->transparent][3] = (stbi_uc) prev_trans; + + return o; + } + + case 0x21: // Comment Extension. + { + int len; + if (stbi__get8(s) == 0xF9) { // Graphic Control Extension. + len = stbi__get8(s); + if (len == 4) { + g->eflags = stbi__get8(s); + g->delay = stbi__get16le(s); + g->transparent = stbi__get8(s); + } else { + stbi__skip(s, len); + break; + } + } + while ((len = stbi__get8(s)) != 0) + stbi__skip(s, len); + break; + } + + case 0x3B: // gif stream termination code + return (stbi_uc *) s; // using '1' causes warning on some compilers + + default: + return stbi__errpuc("unknown code", "Corrupt GIF"); + } + } + + STBI_NOTUSED(req_comp); +} + +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *u = 0; + stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); + memset(g, 0, sizeof(*g)); + STBI_NOTUSED(ri); + + u = stbi__gif_load_next(s, g, comp, req_comp); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + if (u) { + *x = g->w; + *y = g->h; + if (req_comp && req_comp != 4) + u = stbi__convert_format(u, 4, req_comp, g->w, g->h); + } + else if (g->out) + STBI_FREE(g->out); + STBI_FREE(g); + return u; +} + +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) +{ + return stbi__gif_info_raw(s,x,y,comp); +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR loader +// originally by Nicolas Schulz +#ifndef STBI_NO_HDR +static int stbi__hdr_test_core(stbi__context *s, const char *signature) +{ + int i; + for (i=0; signature[i]; ++i) + if (stbi__get8(s) != signature[i]) + return 0; + stbi__rewind(s); + return 1; +} + +static int stbi__hdr_test(stbi__context* s) +{ + int r = stbi__hdr_test_core(s, "#?RADIANCE\n"); + stbi__rewind(s); + if(!r) { + r = stbi__hdr_test_core(s, "#?RGBE\n"); + stbi__rewind(s); + } + return r; +} + +#define STBI__HDR_BUFLEN 1024 +static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) +{ + int len=0; + char c = '\0'; + + c = (char) stbi__get8(z); + + while (!stbi__at_eof(z) && c != '\n') { + buffer[len++] = c; + if (len == STBI__HDR_BUFLEN-1) { + // flush to end of line + while (!stbi__at_eof(z) && stbi__get8(z) != '\n') + ; + break; + } + c = (char) stbi__get8(z); + } + + buffer[len] = 0; + return buffer; +} + +static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) +{ + if ( input[3] != 0 ) { + float f1; + // Exponent + f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); + if (req_comp <= 2) + output[0] = (input[0] + input[1] + input[2]) * f1 / 3; + else { + output[0] = input[0] * f1; + output[1] = input[1] * f1; + output[2] = input[2] * f1; + } + if (req_comp == 2) output[1] = 1; + if (req_comp == 4) output[3] = 1; + } else { + switch (req_comp) { + case 4: output[3] = 1; /* fallthrough */ + case 3: output[0] = output[1] = output[2] = 0; + break; + case 2: output[1] = 1; /* fallthrough */ + case 1: output[0] = 0; + break; + } + } +} + +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int width, height; + stbi_uc *scanline; + float *hdr_data; + int len; + unsigned char count, value; + int i, j, k, c1,c2, z; + const char *headerToken; + STBI_NOTUSED(ri); + + // Check identifier + headerToken = stbi__hdr_gettoken(s,buffer); + if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0) + return stbi__errpf("not HDR", "Corrupt HDR image"); + + // Parse header + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); + + // Parse width and height + // can't use sscanf() if we're not using stdio! + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + height = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + width = (int) strtol(token, NULL, 10); + + *x = width; + *y = height; + + if (comp) *comp = 3; + if (req_comp == 0) req_comp = 3; + + if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0)) + return stbi__errpf("too large", "HDR image is too large"); + + // Read data + hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0); + if (!hdr_data) + return stbi__errpf("outofmem", "Out of memory"); + + // Load image data + // image data is stored as some number of sca + if ( width < 8 || width >= 32768) { + // Read flat data + for (j=0; j < height; ++j) { + for (i=0; i < width; ++i) { + stbi_uc rgbe[4]; + main_decode_loop: + stbi__getn(s, rgbe, 4); + stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); + } + } + } else { + // Read RLE-encoded data + scanline = NULL; + + for (j = 0; j < height; ++j) { + c1 = stbi__get8(s); + c2 = stbi__get8(s); + len = stbi__get8(s); + if (c1 != 2 || c2 != 2 || (len & 0x80)) { + // not run-length encoded, so we have to actually use THIS data as a decoded + // pixel (note this can't be a valid pixel--one of RGB must be >= 128) + stbi_uc rgbe[4]; + rgbe[0] = (stbi_uc) c1; + rgbe[1] = (stbi_uc) c2; + rgbe[2] = (stbi_uc) len; + rgbe[3] = (stbi_uc) stbi__get8(s); + stbi__hdr_convert(hdr_data, rgbe, req_comp); + i = 1; + j = 0; + STBI_FREE(scanline); + goto main_decode_loop; // yes, this makes no sense + } + len <<= 8; + len |= stbi__get8(s); + if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } + if (scanline == NULL) { + scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0); + if (!scanline) { + STBI_FREE(hdr_data); + return stbi__errpf("outofmem", "Out of memory"); + } + } + + for (k = 0; k < 4; ++k) { + int nleft; + i = 0; + while ((nleft = width - i) > 0) { + count = stbi__get8(s); + if (count > 128) { + // Run + value = stbi__get8(s); + count -= 128; + if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = value; + } else { + // Dump + if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = stbi__get8(s); + } + } + } + for (i=0; i < width; ++i) + stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); + } + if (scanline) + STBI_FREE(scanline); + } + + return hdr_data; +} + +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int dummy; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (stbi__hdr_test(s) == 0) { + stbi__rewind( s ); + return 0; + } + + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) { + stbi__rewind( s ); + return 0; + } + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *y = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *x = (int) strtol(token, NULL, 10); + *comp = 3; + return 1; +} +#endif // STBI_NO_HDR + +#ifndef STBI_NO_BMP +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) +{ + void *p; + stbi__bmp_data info; + + info.all_a = 255; + p = stbi__bmp_parse_header(s, &info); + stbi__rewind( s ); + if (p == NULL) + return 0; + if (x) *x = s->img_x; + if (y) *y = s->img_y; + if (comp) *comp = info.ma ? 4 : 3; + return 1; +} +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) +{ + int channelCount, dummy; + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + *y = stbi__get32be(s); + *x = stbi__get32be(s); + if (stbi__get16be(s) != 8) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 3) { + stbi__rewind( s ); + return 0; + } + *comp = 4; + return 1; +} +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) +{ + int act_comp=0,num_packets=0,chained,dummy; + stbi__pic_packet packets[10]; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { + stbi__rewind(s); + return 0; + } + + stbi__skip(s, 88); + + *x = stbi__get16be(s); + *y = stbi__get16be(s); + if (stbi__at_eof(s)) { + stbi__rewind( s); + return 0; + } + if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { + stbi__rewind( s ); + return 0; + } + + stbi__skip(s, 8); + + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return 0; + + packet = &packets[num_packets++]; + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + act_comp |= packet->channel; + + if (stbi__at_eof(s)) { + stbi__rewind( s ); + return 0; + } + if (packet->size != 8) { + stbi__rewind( s ); + return 0; + } + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); + + return 1; +} +#endif + +// ************************************************************************************************* +// Portable Gray Map and Portable Pixel Map loader +// by Ken Miller +// +// PGM: http://netpbm.sourceforge.net/doc/pgm.html +// PPM: http://netpbm.sourceforge.net/doc/ppm.html +// +// Known limitations: +// Does not support comments in the header section +// Does not support ASCII image data (formats P2 and P3) +// Does not support 16-bit-per-channel + +#ifndef STBI_NO_PNM + +static int stbi__pnm_test(stbi__context *s) +{ + char p, t; + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind( s ); + return 0; + } + return 1; +} + +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + STBI_NOTUSED(ri); + + if (!stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n)) + return 0; + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + + if (!stbi__mad3sizes_valid(s->img_n, s->img_x, s->img_y, 0)) + return stbi__errpuc("too large", "PNM too large"); + + out = (stbi_uc *) stbi__malloc_mad3(s->img_n, s->img_x, s->img_y, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + stbi__getn(s, out, s->img_n * s->img_x * s->img_y); + + if (req_comp && req_comp != s->img_n) { + out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + return out; +} + +static int stbi__pnm_isspace(char c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; +} + +static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) +{ + for (;;) { + while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) + *c = (char) stbi__get8(s); + + if (stbi__at_eof(s) || *c != '#') + break; + + while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) + *c = (char) stbi__get8(s); + } +} + +static int stbi__pnm_isdigit(char c) +{ + return c >= '0' && c <= '9'; +} + +static int stbi__pnm_getinteger(stbi__context *s, char *c) +{ + int value = 0; + + while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { + value = value*10 + (*c - '0'); + *c = (char) stbi__get8(s); + } + + return value; +} + +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) +{ + int maxv, dummy; + char c, p, t; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + stbi__rewind(s); + + // Get identifier + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind(s); + return 0; + } + + *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm + + c = (char) stbi__get8(s); + stbi__pnm_skip_whitespace(s, &c); + + *x = stbi__pnm_getinteger(s, &c); // read width + stbi__pnm_skip_whitespace(s, &c); + + *y = stbi__pnm_getinteger(s, &c); // read height + stbi__pnm_skip_whitespace(s, &c); + + maxv = stbi__pnm_getinteger(s, &c); // read max value + + if (maxv > 255) + return stbi__err("max value > 255", "PPM image not 8-bit"); + else + return 1; +} +#endif + +static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) +{ + #ifndef STBI_NO_JPEG + if (stbi__jpeg_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNG + if (stbi__png_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_GIF + if (stbi__gif_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_BMP + if (stbi__bmp_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PIC + if (stbi__pic_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_DDS + if (stbi__dds_info(s, x, y, comp, NULL)) return 1; + #endif + + #ifndef STBI_NO_PVR + if (stbi__pvr_info(s, x, y, comp, NULL)) return 1; + #endif + + #ifndef STBI_NO_PKM + if (stbi__pkm_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_info(s, x, y, comp)) return 1; + #endif + + // test tga last because it's a crappy test! + #ifndef STBI_NO_TGA + if (stbi__tga_info(s, x, y, comp)) + return 1; + #endif + return stbi__err("unknown image type", "Image not of any known type, or corrupt"); +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_info_from_file(f, x, y, comp); + fclose(f); + return result; +} + +STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__info_main(&s,x,y,comp); + fseek(f,pos,SEEK_SET); + return r; +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__info_main(&s,x,y,comp); +} + +// add in my DDS loading support +#ifndef STBI_NO_DDS +#include "stbi_DDS_c.h" +#endif + +// add in my pvr loading support +#ifndef STBI_NO_PVR +#include "stbi_pvr_c.h" +#endif + +// add in my pkm ( ETC1 ) loading support +#ifndef STBI_NO_PKM +#include "stbi_pkm_c.h" +#endif + +#ifndef STBI_NO_EXT +#include "stbi_ext_c.h" +#endif + +#endif // STB_IMAGE_IMPLEMENTATION + +/* + revision history: + 2.15 (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode; + warning fixes; disable run-time SSE detection on gcc; + uniform handling of optional "return" values; + thread-safe initialization of zlib tables + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-11-29) add 16-bit API, only supported for PNG right now + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) allocate large structures on the stack + remove white matting for transparent PSD + fix reported channel count for PNG & BMP + re-enable SSE2 in non-gcc 64-bit + support RGB-formatted JPEG + read 16-bit PNGs (only as 8-bit) + 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED + 2.09 (2016-01-16) allow comments in PNM files + 16-bit-per-pixel TGA (not bit-per-component) + info() for TGA could break due to .hdr handling + info() for BMP to shares code instead of sloppy parse + can use STBI_REALLOC_SIZED if allocator doesn't support realloc + code cleanup + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) fix compiler warnings + partial animated GIF support + limited 16-bpc PSD support + #ifdef unused functions + bug with < 92 byte PIC,PNM,HDR,TGA + 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) extra corruption checking (mmozeiko) + stbi_set_flip_vertically_on_load (nguillemot) + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 + 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG + 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) + progressive JPEG (stb) + PGM/PPM support (Ken Miller) + STBI_MALLOC,STBI_REALLOC,STBI_FREE + GIF bugfix -- seemingly never worked + STBI_NO_*, STBI_ONLY_* + 1.48 (2014-12-14) fix incorrectly-named assert() + 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) + optimize PNG (ryg) + fix bug in interlaced PNG with user-specified channel count (stb) + 1.46 (2014-08-26) + fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG + 1.45 (2014-08-16) + fix MSVC-ARM internal compiler error by wrapping malloc + 1.44 (2014-08-07) + various warning fixes from Ronny Chevalier + 1.43 (2014-07-15) + fix MSVC-only compiler problem in code changed in 1.42 + 1.42 (2014-07-09) + don't define _CRT_SECURE_NO_WARNINGS (affects user code) + fixes to stbi__cleanup_jpeg path + added STBI_ASSERT to avoid requiring assert.h + 1.41 (2014-06-25) + fix search&replace from 1.36 that messed up comments/error messages + 1.40 (2014-06-22) + fix gcc struct-initialization warning + 1.39 (2014-06-15) + fix to TGA optimization when req_comp != number of components in TGA; + fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) + add support for BMP version 5 (more ignored fields) + 1.38 (2014-06-06) + suppress MSVC warnings on integer casts truncating values + fix accidental rename of 'skip' field of I/O + 1.37 (2014-06-04) + remove duplicate typedef + 1.36 (2014-06-03) + convert to header file single-file library + if de-iphone isn't set, load iphone images color-swapped instead of returning NULL + 1.35 (2014-05-27) + various warnings + fix broken STBI_SIMD path + fix bug where stbi_load_from_file no longer left file pointer in correct place + fix broken non-easy path for 32-bit BMP (possibly never used) + TGA optimization by Arseny Kapoulkine + 1.34 (unknown) + use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case + 1.33 (2011-07-14) + make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements + 1.32 (2011-07-13) + support for "info" function for all supported filetypes (SpartanJ) + 1.31 (2011-06-20) + a few more leak fixes, bug in PNG handling (SpartanJ) + 1.30 (2011-06-11) + added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) + removed deprecated format-specific test/load functions + removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway + error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) + fix inefficiency in decoding 32-bit BMP (David Woo) + 1.29 (2010-08-16) + various warning fixes from Aurelien Pocheville + 1.28 (2010-08-01) + fix bug in GIF palette transparency (SpartanJ) + 1.27 (2010-08-01) + cast-to-stbi_uc to fix warnings + 1.26 (2010-07-24) + fix bug in file buffering for PNG reported by SpartanJ + 1.25 (2010-07-17) + refix trans_data warning (Won Chun) + 1.24 (2010-07-12) + perf improvements reading from files on platforms with lock-heavy fgetc() + minor perf improvements for jpeg + deprecated type-specific functions so we'll get feedback if they're needed + attempt to fix trans_data warning (Won Chun) + 1.23 fixed bug in iPhone support + 1.22 (2010-07-10) + removed image *writing* support + stbi_info support from Jetro Lauha + GIF support from Jean-Marc Lienher + iPhone PNG-extensions from James Brown + warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) + 1.21 fix use of 'stbi_uc' in header (reported by jon blow) + 1.20 added support for Softimage PIC, by Tom Seddon + 1.19 bug in interlaced PNG corruption check (found by ryg) + 1.18 (2008-08-02) + fix a threading bug (local mutable static) + 1.17 support interlaced PNG + 1.16 major bugfix - stbi__convert_format converted one too many pixels + 1.15 initialize some fields for thread safety + 1.14 fix threadsafe conversion bug + header-file-only version (#define STBI_HEADER_FILE_ONLY before including) + 1.13 threadsafe + 1.12 const qualifiers in the API + 1.11 Support installable IDCT, colorspace conversion routines + 1.10 Fixes for 64-bit (don't use "unsigned long") + optimized upsampling by Fabian "ryg" Giesen + 1.09 Fix format-conversion for PSD code (bad global variables!) + 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz + 1.07 attempt to fix C++ warning/errors again + 1.06 attempt to fix C++ warning/errors again + 1.05 fix TGA loading to return correct *comp and use good luminance calc + 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free + 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR + 1.02 support for (subset of) HDR files, float interface for preferred access to them + 1.01 fix bug: possible bug in handling right-side up bmps... not sure + fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all + 1.00 interface to zlib that skips zlib header + 0.99 correct handling of alpha in palette + 0.98 TGA loader by lonesock; dynamically add loaders (untested) + 0.97 jpeg errors on too large a file; also catch another malloc failure + 0.96 fix detection of invalid v value - particleman@mollyrocket forum + 0.95 during header scan, seek to markers in case of padding + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same + 0.93 handle jpegtran output; verbose errors + 0.92 read 4,8,16,24,32-bit BMP files of several formats + 0.91 output 24-bit Windows 3.0 BMP files + 0.90 fix a few more warnings; bump version number to approach 1.0 + 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd + 0.60 fix compiling as c++ + 0.59 fix warnings: merge Dave Moore's -Wall fixes + 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian + 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available + 0.56 fix bug: zlib uncompressed mode len vs. nlen + 0.55 fix bug: restart_interval not initialized to 0 + 0.54 allow NULL for 'int *comp' + 0.53 fix bug in png 3->4; speedup png decoding + 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments + 0.51 obey req_comp requests, 1-component jpegs return as 1-component, + on 'test' only check type, not whether we support this variant + 0.50 (2006-11-19) + first released version +*/ + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/lib/SOIL2/stb_image_write.h b/lib/SOIL2/stb_image_write.h new file mode 100644 index 000000000..df623393d --- /dev/null +++ b/lib/SOIL2/stb_image_write.h @@ -0,0 +1,1092 @@ +/* stb_image_write - v1.05 - public domain - http://nothings.org/stb/stb_image_write.h + writes out PNG/BMP/TGA images to C stdio - Sean Barrett 2010-2015 + no warranty implied; use at your own risk + + Before #including, + + #define STB_IMAGE_WRITE_IMPLEMENTATION + + in the file that you want to have the implementation. + + Will probably not work correctly with strict-aliasing optimizations. + +ABOUT: + + This header file is a library for writing images to C stdio. It could be + adapted to write to memory or a general streaming interface; let me know. + + The PNG output is not optimal; it is 20-50% larger than the file + written by a decent optimizing implementation. This library is designed + for source code compactness and simplicity, not optimal image file size + or run-time performance. + +BUILDING: + + You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h. + You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace + malloc,realloc,free. + You can define STBIW_MEMMOVE() to replace memmove() + +USAGE: + + There are four functions, one for each image file format: + + int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); + + There are also four equivalent functions that use an arbitrary write function. You are + expected to open/close your file-equivalent before and after calling these: + + int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); + + where the callback is: + void stbi_write_func(void *context, void *data, int size); + + You can define STBI_WRITE_NO_STDIO to disable the file variant of these + functions, so the library will not use stdio.h at all. However, this will + also disable HDR writing, because it requires stdio for formatted output. + + Each function returns 0 on failure and non-0 on success. + + The functions create an image file defined by the parameters. The image + is a rectangle of pixels stored from left-to-right, top-to-bottom. + Each pixel contains 'comp' channels of data stored interleaved with 8-bits + per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is + monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall. + The *data pointer points to the first byte of the top-left-most pixel. + For PNG, "stride_in_bytes" is the distance in bytes from the first byte of + a row of pixels to the first byte of the next row of pixels. + + PNG creates output files with the same number of components as the input. + The BMP format expands Y to RGB in the file format and does not + output alpha. + + PNG supports writing rectangles of data even when the bytes storing rows of + data are not consecutive in memory (e.g. sub-rectangles of a larger image), + by supplying the stride between the beginning of adjacent rows. The other + formats do not. (Thus you cannot write a native-format BMP through the BMP + writer, both because it is in BGR order and because it may have padding + at the end of the line.) + + HDR expects linear float data. Since the format is always 32-bit rgb(e) + data, alpha (if provided) is discarded, and for monochrome data it is + replicated across all three channels. + + TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed + data, set the global variable 'stbi_write_tga_with_rle' to 0. + +CREDITS: + + PNG/BMP/TGA + Sean Barrett + HDR + Baldur Karlsson + TGA monochrome: + Jean-Sebastien Guay + misc enhancements: + Tim Kelsey + TGA RLE + Alan Hickman + initial file IO callback implementation + Emmanuel Julien + bugfixes: + github:Chribba + Guillaume Chereau + github:jry2 + github:romigrou + Sergio Gonzalez + Jonas Karlsson + Filip Wasil + Thatcher Ulrich + github:poppolopoppo + Patrick Boettcher + +LICENSE + + See end of file for license information. + +*/ + +#ifndef INCLUDE_STB_IMAGE_WRITE_H +#define INCLUDE_STB_IMAGE_WRITE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef STB_IMAGE_WRITE_STATIC +#define STBIWDEF static +#else +#define STBIWDEF extern +extern int stbi_write_tga_with_rle; +#endif + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); +STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); +#endif + +typedef void stbi_write_func(void *context, void *data, int size); + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); + +#ifdef __cplusplus +} +#endif + +#endif//INCLUDE_STB_IMAGE_WRITE_H + +#ifdef STB_IMAGE_WRITE_IMPLEMENTATION + +#ifdef _WIN32 + #ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS + #endif + #ifndef _CRT_NONSTDC_NO_DEPRECATE + #define _CRT_NONSTDC_NO_DEPRECATE + #endif +#endif + +#ifndef STBI_WRITE_NO_STDIO +#include +#endif // STBI_WRITE_NO_STDIO + +#include +#include +#include +#include + +#if defined(STBIW_MALLOC) && defined(STBIW_FREE) && (defined(STBIW_REALLOC) || defined(STBIW_REALLOC_SIZED)) +// ok +#elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC) && !defined(STBIW_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC (or STBIW_REALLOC_SIZED)." +#endif + +#ifndef STBIW_MALLOC +#define STBIW_MALLOC(sz) malloc(sz) +#define STBIW_REALLOC(p,newsz) realloc(p,newsz) +#define STBIW_FREE(p) free(p) +#endif + +#ifndef STBIW_REALLOC_SIZED +#define STBIW_REALLOC_SIZED(p,oldsz,newsz) STBIW_REALLOC(p,newsz) +#endif + + +#ifndef STBIW_MEMMOVE +#define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz) +#endif + + +#ifndef STBIW_ASSERT +#include +#define STBIW_ASSERT(x) assert(x) +#endif + +#define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff) + +typedef struct +{ + stbi_write_func *func; + void *context; +} stbi__write_context; + +// initialize a callback-based context +static void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func *c, void *context) +{ + s->func = c; + s->context = context; +} + +#ifndef STBI_WRITE_NO_STDIO + +static void stbi__stdio_write(void *context, void *data, int size) +{ + fwrite(data,1,size,(FILE*) context); +} + +static int stbi__start_write_file(stbi__write_context *s, const char *filename) +{ + FILE *f = fopen(filename, "wb"); + stbi__start_write_callbacks(s, stbi__stdio_write, (void *) f); + return f != NULL; +} + +static void stbi__end_write_file(stbi__write_context *s) +{ + fclose((FILE *)s->context); +} + +#endif // !STBI_WRITE_NO_STDIO + +typedef unsigned int stbiw_uint32; +typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1]; + +#ifdef STB_IMAGE_WRITE_STATIC +static int stbi_write_tga_with_rle = 1; +#else +int stbi_write_tga_with_rle = 1; +#endif + +static void stbiw__writefv(stbi__write_context *s, const char *fmt, va_list v) +{ + while (*fmt) { + switch (*fmt++) { + case ' ': break; + case '1': { unsigned char x = STBIW_UCHAR(va_arg(v, int)); + s->func(s->context,&x,1); + break; } + case '2': { int x = va_arg(v,int); + unsigned char b[2]; + b[0] = STBIW_UCHAR(x); + b[1] = STBIW_UCHAR(x>>8); + s->func(s->context,b,2); + break; } + case '4': { stbiw_uint32 x = va_arg(v,int); + unsigned char b[4]; + b[0]=STBIW_UCHAR(x); + b[1]=STBIW_UCHAR(x>>8); + b[2]=STBIW_UCHAR(x>>16); + b[3]=STBIW_UCHAR(x>>24); + s->func(s->context,b,4); + break; } + default: + STBIW_ASSERT(0); + return; + } + } +} + +static void stbiw__writef(stbi__write_context *s, const char *fmt, ...) +{ + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); +} + +static void stbiw__write3(stbi__write_context *s, unsigned char a, unsigned char b, unsigned char c) +{ + unsigned char arr[3]; + arr[0] = a, arr[1] = b, arr[2] = c; + s->func(s->context, arr, 3); +} + +static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, int write_alpha, int expand_mono, unsigned char *d) +{ + unsigned char bg[3] = { 255, 0, 255}, px[3]; + int k; + + if (write_alpha < 0) + s->func(s->context, &d[comp - 1], 1); + + switch (comp) { + case 2: // 2 pixels = mono + alpha, alpha is written separately, so same as 1-channel case + case 1: + if (expand_mono) + stbiw__write3(s, d[0], d[0], d[0]); // monochrome bmp + else + s->func(s->context, d, 1); // monochrome TGA + break; + case 4: + if (!write_alpha) { + // composite against pink background + for (k = 0; k < 3; ++k) + px[k] = bg[k] + ((d[k] - bg[k]) * d[3]) / 255; + stbiw__write3(s, px[1 - rgb_dir], px[1], px[1 + rgb_dir]); + break; + } + /* FALLTHROUGH */ + case 3: + stbiw__write3(s, d[1 - rgb_dir], d[1], d[1 + rgb_dir]); + break; + } + if (write_alpha > 0) + s->func(s->context, &d[comp - 1], 1); +} + +static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono) +{ + stbiw_uint32 zero = 0; + int i,j, j_end; + + if (y <= 0) + return; + + if (vdir < 0) + j_end = -1, j = y-1; + else + j_end = y, j = 0; + + for (; j != j_end; j += vdir) { + for (i=0; i < x; ++i) { + unsigned char *d = (unsigned char *) data + (j*x+i)*comp; + stbiw__write_pixel(s, rgb_dir, comp, write_alpha, expand_mono, d); + } + s->func(s->context, &zero, scanline_pad); + } +} + +static int stbiw__outfile(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, int expand_mono, void *data, int alpha, int pad, const char *fmt, ...) +{ + if (y < 0 || x < 0) { + return 0; + } else { + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); + stbiw__write_pixels(s,rgb_dir,vdir,x,y,comp,data,alpha,pad, expand_mono); + return 1; + } +} + +static int stbi_write_bmp_core(stbi__write_context *s, int x, int y, int comp, const void *data) +{ + int pad = (-x*3) & 3; + return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *) data,0,pad, + "11 4 22 4" "4 44 22 444444", + 'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header + 40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header +} + +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) +{ + stbi__write_context s; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_bmp_core(&s, x, y, comp, data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data) +{ + stbi__write_context s; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_bmp_core(&s, x, y, comp, data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif //!STBI_WRITE_NO_STDIO + +static int stbi_write_tga_core(stbi__write_context *s, int x, int y, int comp, void *data) +{ + int has_alpha = (comp == 2 || comp == 4); + int colorbytes = has_alpha ? comp-1 : comp; + int format = colorbytes < 2 ? 3 : 2; // 3 color channels (RGB/RGBA) = 2, 1 color channel (Y/YA) = 3 + + if (y < 0 || x < 0) + return 0; + + if (!stbi_write_tga_with_rle) { + return stbiw__outfile(s, -1, -1, x, y, comp, 0, (void *) data, has_alpha, 0, + "111 221 2222 11", 0, 0, format, 0, 0, 0, 0, 0, x, y, (colorbytes + has_alpha) * 8, has_alpha * 8); + } else { + int i,j,k; + + stbiw__writef(s, "111 221 2222 11", 0,0,format+8, 0,0,0, 0,0,x,y, (colorbytes + has_alpha) * 8, has_alpha * 8); + + for (j = y - 1; j >= 0; --j) { + unsigned char *row = (unsigned char *) data + j * x * comp; + int len; + + for (i = 0; i < x; i += len) { + unsigned char *begin = row + i * comp; + int diff = 1; + len = 1; + + if (i < x - 1) { + ++len; + diff = memcmp(begin, row + (i + 1) * comp, comp); + if (diff) { + const unsigned char *prev = begin; + for (k = i + 2; k < x && len < 128; ++k) { + if (memcmp(prev, row + k * comp, comp)) { + prev += comp; + ++len; + } else { + --len; + break; + } + } + } else { + for (k = i + 2; k < x && len < 128; ++k) { + if (!memcmp(begin, row + k * comp, comp)) { + ++len; + } else { + break; + } + } + } + } + + if (diff) { + unsigned char header = STBIW_UCHAR(len - 1); + s->func(s->context, &header, 1); + for (k = 0; k < len; ++k) { + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin + k * comp); + } + } else { + unsigned char header = STBIW_UCHAR(len - 129); + s->func(s->context, &header, 1); + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin); + } + } + } + } + return 1; +} + +int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) +{ + stbi__write_context s; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_tga_core(&s, x, y, comp, (void *) data); +} + +#ifndef STBI_WRITE_NO_STDIO +int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data) +{ + stbi__write_context s; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_tga_core(&s, x, y, comp, (void *) data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR writer +// by Baldur Karlsson + +#define stbiw__max(a, b) ((a) > (b) ? (a) : (b)) + +void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear) +{ + int exponent; + float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2])); + + if (maxcomp < 1e-32f) { + rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; + } else { + float normalize = (float) frexp(maxcomp, &exponent) * 256.0f/maxcomp; + + rgbe[0] = (unsigned char)(linear[0] * normalize); + rgbe[1] = (unsigned char)(linear[1] * normalize); + rgbe[2] = (unsigned char)(linear[2] * normalize); + rgbe[3] = (unsigned char)(exponent + 128); + } +} + +void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char databyte) +{ + unsigned char lengthbyte = STBIW_UCHAR(length+128); + STBIW_ASSERT(length+128 <= 255); + s->func(s->context, &lengthbyte, 1); + s->func(s->context, &databyte, 1); +} + +void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *data) +{ + unsigned char lengthbyte = STBIW_UCHAR(length); + STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code + s->func(s->context, &lengthbyte, 1); + s->func(s->context, data, length); +} + +void stbiw__write_hdr_scanline(stbi__write_context *s, int width, int ncomp, unsigned char *scratch, float *scanline) +{ + unsigned char scanlineheader[4] = { 2, 2, 0, 0 }; + unsigned char rgbe[4]; + float linear[3]; + int x; + + scanlineheader[2] = (width&0xff00)>>8; + scanlineheader[3] = (width&0x00ff); + + /* skip RLE for images too small or large */ + if (width < 8 || width >= 32768) { + for (x=0; x < width; x++) { + switch (ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*ncomp + 2]; + linear[1] = scanline[x*ncomp + 1]; + linear[0] = scanline[x*ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + s->func(s->context, rgbe, 4); + } + } else { + int c,r; + /* encode into scratch buffer */ + for (x=0; x < width; x++) { + switch(ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*ncomp + 2]; + linear[1] = scanline[x*ncomp + 1]; + linear[0] = scanline[x*ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + scratch[x + width*0] = rgbe[0]; + scratch[x + width*1] = rgbe[1]; + scratch[x + width*2] = rgbe[2]; + scratch[x + width*3] = rgbe[3]; + } + + s->func(s->context, scanlineheader, 4); + + /* RLE each component separately */ + for (c=0; c < 4; c++) { + unsigned char *comp = &scratch[width*c]; + + x = 0; + while (x < width) { + // find first run + r = x; + while (r+2 < width) { + if (comp[r] == comp[r+1] && comp[r] == comp[r+2]) + break; + ++r; + } + if (r+2 >= width) + r = width; + // dump up to first run + while (x < r) { + int len = r-x; + if (len > 128) len = 128; + stbiw__write_dump_data(s, len, &comp[x]); + x += len; + } + // if there's a run, output it + if (r+2 < width) { // same test as what we break out of in search loop, so only true if we break'd + // find next byte after run + while (r < width && comp[r] == comp[x]) + ++r; + // output run up to r + while (x < r) { + int len = r-x; + if (len > 127) len = 127; + stbiw__write_run_data(s, len, comp[x]); + x += len; + } + } + } + } + } +} + +static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, float *data) +{ + if (y <= 0 || x <= 0 || data == NULL) + return 0; + else { + // Each component is stored separately. Allocate scratch space for full output scanline. + unsigned char *scratch = (unsigned char *) STBIW_MALLOC(x*4); + int i, len; + char buffer[128]; + char header[] = "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n"; + s->func(s->context, header, sizeof(header)-1); + + len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); + s->func(s->context, buffer, len); + + for(i=0; i < y; i++) + stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp*i*x); + STBIW_FREE(scratch); + return 1; + } +} + +int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const float *data) +{ + stbi__write_context s; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_hdr_core(&s, x, y, comp, (float *) data); +} + +#ifndef STBI_WRITE_NO_STDIO +int stbi_write_hdr(char const *filename, int x, int y, int comp, const float *data) +{ + stbi__write_context s; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_hdr_core(&s, x, y, comp, (float *) data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif // STBI_WRITE_NO_STDIO + + +////////////////////////////////////////////////////////////////////////////// +// +// PNG writer +// + +// stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size() +#define stbiw__sbraw(a) ((int *) (a) - 2) +#define stbiw__sbm(a) stbiw__sbraw(a)[0] +#define stbiw__sbn(a) stbiw__sbraw(a)[1] + +#define stbiw__sbneedgrow(a,n) ((a)==0 || stbiw__sbn(a)+n >= stbiw__sbm(a)) +#define stbiw__sbmaybegrow(a,n) (stbiw__sbneedgrow(a,(n)) ? stbiw__sbgrow(a,n) : 0) +#define stbiw__sbgrow(a,n) stbiw__sbgrowf((void **) &(a), (n), sizeof(*(a))) + +#define stbiw__sbpush(a, v) (stbiw__sbmaybegrow(a,1), (a)[stbiw__sbn(a)++] = (v)) +#define stbiw__sbcount(a) ((a) ? stbiw__sbn(a) : 0) +#define stbiw__sbfree(a) ((a) ? STBIW_FREE(stbiw__sbraw(a)),0 : 0) + +static void *stbiw__sbgrowf(void **arr, int increment, int itemsize) +{ + int m = *arr ? 2*stbiw__sbm(*arr)+increment : increment+1; + void *p = STBIW_REALLOC_SIZED(*arr ? stbiw__sbraw(*arr) : 0, *arr ? (stbiw__sbm(*arr)*itemsize + sizeof(int)*2) : 0, itemsize * m + sizeof(int)*2); + STBIW_ASSERT(p); + if (p) { + if (!*arr) ((int *) p)[1] = 0; + *arr = (void *) ((int *) p + 2); + stbiw__sbm(*arr) = m; + } + return *arr; +} + +static unsigned char *stbiw__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount) +{ + while (*bitcount >= 8) { + stbiw__sbpush(data, STBIW_UCHAR(*bitbuffer)); + *bitbuffer >>= 8; + *bitcount -= 8; + } + return data; +} + +static int stbiw__zlib_bitrev(int code, int codebits) +{ + int res=0; + while (codebits--) { + res = (res << 1) | (code & 1); + code >>= 1; + } + return res; +} + +static unsigned int stbiw__zlib_countm(unsigned char *a, unsigned char *b, int limit) +{ + int i; + for (i=0; i < limit && i < 258; ++i) + if (a[i] != b[i]) break; + return i; +} + +static unsigned int stbiw__zhash(unsigned char *data) +{ + stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16); + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + return hash; +} + +#define stbiw__zlib_flush() (out = stbiw__zlib_flushf(out, &bitbuf, &bitcount)) +#define stbiw__zlib_add(code,codebits) \ + (bitbuf |= (code) << bitcount, bitcount += (codebits), stbiw__zlib_flush()) +#define stbiw__zlib_huffa(b,c) stbiw__zlib_add(stbiw__zlib_bitrev(b,c),c) +// default huffman tables +#define stbiw__zlib_huff1(n) stbiw__zlib_huffa(0x30 + (n), 8) +#define stbiw__zlib_huff2(n) stbiw__zlib_huffa(0x190 + (n)-144, 9) +#define stbiw__zlib_huff3(n) stbiw__zlib_huffa(0 + (n)-256,7) +#define stbiw__zlib_huff4(n) stbiw__zlib_huffa(0xc0 + (n)-280,8) +#define stbiw__zlib_huff(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : (n) <= 255 ? stbiw__zlib_huff2(n) : (n) <= 279 ? stbiw__zlib_huff3(n) : stbiw__zlib_huff4(n)) +#define stbiw__zlib_huffb(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : stbiw__zlib_huff2(n)) + +#define stbiw__ZHASH 16384 + +unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) +{ + static unsigned short lengthc[] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258, 259 }; + static unsigned char lengtheb[]= { 0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; + static unsigned short distc[] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 32768 }; + static unsigned char disteb[] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 }; + unsigned int bitbuf=0; + int i,j, bitcount=0; + unsigned char *out = NULL; + unsigned char ***hash_table = (unsigned char***) STBIW_MALLOC(stbiw__ZHASH * sizeof(char**)); + if (quality < 5) quality = 5; + + stbiw__sbpush(out, 0x78); // DEFLATE 32K window + stbiw__sbpush(out, 0x5e); // FLEVEL = 1 + stbiw__zlib_add(1,1); // BFINAL = 1 + stbiw__zlib_add(1,2); // BTYPE = 1 -- fixed huffman + + for (i=0; i < stbiw__ZHASH; ++i) + hash_table[i] = NULL; + + i=0; + while (i < data_len-3) { + // hash next 3 bytes of data to be compressed + int h = stbiw__zhash(data+i)&(stbiw__ZHASH-1), best=3; + unsigned char *bestloc = 0; + unsigned char **hlist = hash_table[h]; + int n = stbiw__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32768) { // if entry lies within window + int d = stbiw__zlib_countm(hlist[j], data+i, data_len-i); + if (d >= best) best=d,bestloc=hlist[j]; + } + } + // when hash table entry is too long, delete half the entries + if (hash_table[h] && stbiw__sbn(hash_table[h]) == 2*quality) { + STBIW_MEMMOVE(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality); + stbiw__sbn(hash_table[h]) = quality; + } + stbiw__sbpush(hash_table[h],data+i); + + if (bestloc) { + // "lazy matching" - check match at *next* byte, and if it's better, do cur byte as literal + h = stbiw__zhash(data+i+1)&(stbiw__ZHASH-1); + hlist = hash_table[h]; + n = stbiw__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32767) { + int e = stbiw__zlib_countm(hlist[j], data+i+1, data_len-i-1); + if (e > best) { // if next match is better, bail on current match + bestloc = NULL; + break; + } + } + } + } + + if (bestloc) { + int d = (int) (data+i - bestloc); // distance back + STBIW_ASSERT(d <= 32767 && best <= 258); + for (j=0; best > lengthc[j+1]-1; ++j); + stbiw__zlib_huff(j+257); + if (lengtheb[j]) stbiw__zlib_add(best - lengthc[j], lengtheb[j]); + for (j=0; d > distc[j+1]-1; ++j); + stbiw__zlib_add(stbiw__zlib_bitrev(j,5),5); + if (disteb[j]) stbiw__zlib_add(d - distc[j], disteb[j]); + i += best; + } else { + stbiw__zlib_huffb(data[i]); + ++i; + } + } + // write out final bytes + for (;i < data_len; ++i) + stbiw__zlib_huffb(data[i]); + stbiw__zlib_huff(256); // end of block + // pad with 0 bits to byte boundary + while (bitcount) + stbiw__zlib_add(0,1); + + for (i=0; i < stbiw__ZHASH; ++i) + (void) stbiw__sbfree(hash_table[i]); + STBIW_FREE(hash_table); + + { + // compute adler32 on input + unsigned int s1=1, s2=0; + int blocklen = (int) (data_len % 5552); + j=0; + while (j < data_len) { + for (i=0; i < blocklen; ++i) s1 += data[j+i], s2 += s1; + s1 %= 65521, s2 %= 65521; + j += blocklen; + blocklen = 5552; + } + stbiw__sbpush(out, STBIW_UCHAR(s2 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s2)); + stbiw__sbpush(out, STBIW_UCHAR(s1 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s1)); + } + *out_len = stbiw__sbn(out); + // make returned pointer freeable + STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len); + return (unsigned char *) stbiw__sbraw(out); +} + +static unsigned int stbiw__crc32(unsigned char *buffer, int len) +{ + static unsigned int crc_table[256] = + { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, + 0x0eDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, + 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, + 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, + 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, + 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, + 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, + 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, + 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, + 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, + 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, + 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, + 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, + 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, + 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + }; + + unsigned int crc = ~0u; + int i; + for (i=0; i < len; ++i) + crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)]; + return ~crc; +} + +#define stbiw__wpng4(o,a,b,c,d) ((o)[0]=STBIW_UCHAR(a),(o)[1]=STBIW_UCHAR(b),(o)[2]=STBIW_UCHAR(c),(o)[3]=STBIW_UCHAR(d),(o)+=4) +#define stbiw__wp32(data,v) stbiw__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v)); +#define stbiw__wptag(data,s) stbiw__wpng4(data, s[0],s[1],s[2],s[3]) + +static void stbiw__wpcrc(unsigned char **data, int len) +{ + unsigned int crc = stbiw__crc32(*data - len - 4, len+4); + stbiw__wp32(*data, crc); +} + +static unsigned char stbiw__paeth(int a, int b, int c) +{ + int p = a + b - c, pa = abs(p-a), pb = abs(p-b), pc = abs(p-c); + if (pa <= pb && pa <= pc) return STBIW_UCHAR(a); + if (pb <= pc) return STBIW_UCHAR(b); + return STBIW_UCHAR(c); +} + +// @OPTIMIZE: provide an option that always forces left-predict or paeth predict +unsigned char *stbi_write_png_to_mem(unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len) +{ + int ctype[5] = { -1, 0, 4, 2, 6 }; + unsigned char sig[8] = { 137,80,78,71,13,10,26,10 }; + unsigned char *out,*o, *filt, *zlib; + signed char *line_buffer; + int i,j,k,p,zlen; + + if (stride_bytes == 0) + stride_bytes = x * n; + + filt = (unsigned char *) STBIW_MALLOC((x*n+1) * y); if (!filt) return 0; + line_buffer = (signed char *) STBIW_MALLOC(x * n); if (!line_buffer) { STBIW_FREE(filt); return 0; } + for (j=0; j < y; ++j) { + static int mapping[] = { 0,1,2,3,4 }; + static int firstmap[] = { 0,1,0,5,6 }; + int *mymap = (j != 0) ? mapping : firstmap; + int best = 0, bestval = 0x7fffffff; + for (p=0; p < 2; ++p) { + for (k= p?best:0; k < 5; ++k) { // @TODO: clarity: rewrite this to go 0..5, and 'continue' the unwanted ones during 2nd pass + int type = mymap[k],est=0; + unsigned char *z = pixels + stride_bytes*j; + for (i=0; i < n; ++i) + switch (type) { + case 0: line_buffer[i] = z[i]; break; + case 1: line_buffer[i] = z[i]; break; + case 2: line_buffer[i] = z[i] - z[i-stride_bytes]; break; + case 3: line_buffer[i] = z[i] - (z[i-stride_bytes]>>1); break; + case 4: line_buffer[i] = (signed char) (z[i] - stbiw__paeth(0,z[i-stride_bytes],0)); break; + case 5: line_buffer[i] = z[i]; break; + case 6: line_buffer[i] = z[i]; break; + } + for (i=n; i < x*n; ++i) { + switch (type) { + case 0: line_buffer[i] = z[i]; break; + case 1: line_buffer[i] = z[i] - z[i-n]; break; + case 2: line_buffer[i] = z[i] - z[i-stride_bytes]; break; + case 3: line_buffer[i] = z[i] - ((z[i-n] + z[i-stride_bytes])>>1); break; + case 4: line_buffer[i] = z[i] - stbiw__paeth(z[i-n], z[i-stride_bytes], z[i-stride_bytes-n]); break; + case 5: line_buffer[i] = z[i] - (z[i-n]>>1); break; + case 6: line_buffer[i] = z[i] - stbiw__paeth(z[i-n], 0,0); break; + } + } + if (p) break; + for (i=0; i < x*n; ++i) + est += abs((signed char) line_buffer[i]); + if (est < bestval) { bestval = est; best = k; } + } + } + // when we get here, best contains the filter type, and line_buffer contains the data + filt[j*(x*n+1)] = (unsigned char) best; + STBIW_MEMMOVE(filt+j*(x*n+1)+1, line_buffer, x*n); + } + STBIW_FREE(line_buffer); + zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, 8); // increase 8 to get smaller but use more memory + STBIW_FREE(filt); + if (!zlib) return 0; + + // each tag requires 12 bytes of overhead + out = (unsigned char *) STBIW_MALLOC(8 + 12+13 + 12+zlen + 12); + if (!out) return 0; + *out_len = 8 + 12+13 + 12+zlen + 12; + + o=out; + STBIW_MEMMOVE(o,sig,8); o+= 8; + stbiw__wp32(o, 13); // header length + stbiw__wptag(o, "IHDR"); + stbiw__wp32(o, x); + stbiw__wp32(o, y); + *o++ = 8; + *o++ = STBIW_UCHAR(ctype[n]); + *o++ = 0; + *o++ = 0; + *o++ = 0; + stbiw__wpcrc(&o,13); + + stbiw__wp32(o, zlen); + stbiw__wptag(o, "IDAT"); + STBIW_MEMMOVE(o, zlib, zlen); + o += zlen; + STBIW_FREE(zlib); + stbiw__wpcrc(&o, zlen); + + stbiw__wp32(o,0); + stbiw__wptag(o, "IEND"); + stbiw__wpcrc(&o,0); + + STBIW_ASSERT(o == out + *out_len); + + return out; +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes) +{ + FILE *f; + int len; + unsigned char *png = stbi_write_png_to_mem((unsigned char *) data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + f = fopen(filename, "wb"); + if (!f) { STBIW_FREE(png); return 0; } + fwrite(png, 1, len, f); + fclose(f); + STBIW_FREE(png); + return 1; +} +#endif + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int stride_bytes) +{ + int len; + unsigned char *png = stbi_write_png_to_mem((unsigned char *) data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + func(context, png, len); + STBIW_FREE(png); + return 1; +} + +#endif // STB_IMAGE_WRITE_IMPLEMENTATION + +/* Revision history + 1.04 (2017-03-03) + monochrome BMP expansion + 1.03 ??? + 1.02 (2016-04-02) + avoid allocating large structures on the stack + 1.01 (2016-01-16) + STBIW_REALLOC_SIZED: support allocators with no realloc support + avoid race-condition in crc initialization + minor compile issues + 1.00 (2015-09-14) + installable file IO function + 0.99 (2015-09-13) + warning fixes; TGA rle support + 0.98 (2015-04-08) + added STBIW_MALLOC, STBIW_ASSERT etc + 0.97 (2015-01-18) + fixed HDR asserts, rewrote HDR rle logic + 0.96 (2015-01-17) + add HDR output + fix monochrome BMP + 0.95 (2014-08-17) + add monochrome TGA output + 0.94 (2014-05-31) + rename private functions to avoid conflicts with stb_image.h + 0.93 (2014-05-27) + warning fixes + 0.92 (2010-08-01) + casts to unsigned char to fix warnings + 0.91 (2010-07-17) + first public release + 0.90 first internal release +*/ + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/lib/SOIL2/stbi_DDS.h b/lib/SOIL2/stbi_DDS.h new file mode 100644 index 000000000..fbf8193bf --- /dev/null +++ b/lib/SOIL2/stbi_DDS.h @@ -0,0 +1,34 @@ +/* + adding DDS loading support to stbi +*/ + +#ifndef HEADER_STB_IMAGE_DDS_AUGMENTATION +#define HEADER_STB_IMAGE_DDS_AUGMENTATION + +/* is it a DDS file? */ +extern int stbi__dds_test_memory (stbi_uc const *buffer, int len); +extern int stbi__dds_test_callbacks (stbi_io_callbacks const *clbk, void *user); + +extern void *stbi__dds_load_from_path (const char *filename, int *x, int *y, int *comp, int req_comp); +extern void *stbi__dds_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); +extern void *stbi__dds_load_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp); + +#ifndef STBI_NO_STDIO +extern int stbi__dds_test_filename (char const *filename); +extern int stbi__dds_test_file (FILE *f); +extern void *stbi__dds_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +#endif + +extern int stbi__dds_info_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int *iscompressed); +extern int stbi__dds_info_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int *iscompressed); + + +#ifndef STBI_NO_STDIO +extern int stbi__dds_info_from_path (char const *filename, int *x, int *y, int *comp, int *iscompressed); +extern int stbi__dds_info_from_file (FILE *f, int *x, int *y, int *comp, int *iscompressed); +#endif + +/* +// +//// end header file /////////////////////////////////////////////////////*/ +#endif /* HEADER_STB_IMAGE_DDS_AUGMENTATION */ diff --git a/lib/soil/stbi_DDS_aug_c.h b/lib/SOIL2/stbi_DDS_c.h similarity index 67% rename from lib/soil/stbi_DDS_aug_c.h rename to lib/SOIL2/stbi_DDS_c.h index f49407a7f..392065b12 100644 --- a/lib/soil/stbi_DDS_aug_c.h +++ b/lib/SOIL2/stbi_DDS_c.h @@ -2,103 +2,77 @@ /// DDS file support, does decoding, _not_ direct uploading /// (use SOIL for that ;-) -/// A bunch of DirectDraw Surface structures and flags -typedef struct { - unsigned int dwMagic; - unsigned int dwSize; - unsigned int dwFlags; - unsigned int dwHeight; - unsigned int dwWidth; - unsigned int dwPitchOrLinearSize; - unsigned int dwDepth; - unsigned int dwMipMapCount; - unsigned int dwReserved1[ 11 ]; - - // DDPIXELFORMAT - struct { - unsigned int dwSize; - unsigned int dwFlags; - unsigned int dwFourCC; - unsigned int dwRGBBitCount; - unsigned int dwRBitMask; - unsigned int dwGBitMask; - unsigned int dwBBitMask; - unsigned int dwAlphaBitMask; - } sPixelFormat; - - // DDCAPS2 - struct { - unsigned int dwCaps1; - unsigned int dwCaps2; - unsigned int dwDDSX; - unsigned int dwReserved; - } sCaps; - unsigned int dwReserved2; -} DDS_header ; - -// the following constants were copied directly off the MSDN website - -// The dwFlags member of the original DDSURFACEDESC2 structure -// can be set to one or more of the following values. -#define DDSD_CAPS 0x00000001 -#define DDSD_HEIGHT 0x00000002 -#define DDSD_WIDTH 0x00000004 -#define DDSD_PITCH 0x00000008 -#define DDSD_PIXELFORMAT 0x00001000 -#define DDSD_MIPMAPCOUNT 0x00020000 -#define DDSD_LINEARSIZE 0x00080000 -#define DDSD_DEPTH 0x00800000 - -// DirectDraw Pixel Format -#define DDPF_ALPHAPIXELS 0x00000001 -#define DDPF_FOURCC 0x00000004 -#define DDPF_RGB 0x00000040 - -// The dwCaps1 member of the DDSCAPS2 structure can be -// set to one or more of the following values. -#define DDSCAPS_COMPLEX 0x00000008 -#define DDSCAPS_TEXTURE 0x00001000 -#define DDSCAPS_MIPMAP 0x00400000 - -// The dwCaps2 member of the DDSCAPS2 structure can be -// set to one or more of the following values. -#define DDSCAPS2_CUBEMAP 0x00000200 -#define DDSCAPS2_CUBEMAP_POSITIVEX 0x00000400 -#define DDSCAPS2_CUBEMAP_NEGATIVEX 0x00000800 -#define DDSCAPS2_CUBEMAP_POSITIVEY 0x00001000 -#define DDSCAPS2_CUBEMAP_NEGATIVEY 0x00002000 -#define DDSCAPS2_CUBEMAP_POSITIVEZ 0x00004000 -#define DDSCAPS2_CUBEMAP_NEGATIVEZ 0x00008000 -#define DDSCAPS2_VOLUME 0x00200000 - -static int dds_test(stbi *s) +#include "image_DXT.h" + +static int stbi__dds_test(stbi__context *s) { // check the magic number - if (get8(s) != 'D') return 0; - if (get8(s) != 'D') return 0; - if (get8(s) != 'S') return 0; - if (get8(s) != ' ') return 0; + if (stbi__get8(s) != 'D') { + stbi__rewind(s); + return 0; + } + + if (stbi__get8(s) != 'D') { + stbi__rewind(s); + return 0; + } + + if (stbi__get8(s) != 'S') { + stbi__rewind(s); + return 0; + } + + if (stbi__get8(s) != ' ') { + stbi__rewind(s); + return 0; + } + // check header size - if (get32le(s) != 124) return 0; + if (stbi__get32le(s) != 124) { + stbi__rewind(s); + return 0; + } + + // Also rewind because the loader needs to read the header + stbi__rewind(s); + return 1; } #ifndef STBI_NO_STDIO -int stbi_dds_test_file (FILE *f) + +int stbi__dds_test_filename (char const *filename) +{ + int r; + FILE *f = fopen(filename, "rb"); + if (!f) return 0; + r = stbi__dds_test_file(f); + fclose(f); + return r; +} + +int stbi__dds_test_file (FILE *f) { - stbi s; + stbi__context s; int r,n = ftell(f); - start_file(&s,f); - r = dds_test(&s); + stbi__start_file(&s,f); + r = stbi__dds_test(&s); fseek(f,n,SEEK_SET); return r; } #endif -int stbi_dds_test_memory (stbi_uc const *buffer, int len) +int stbi__dds_test_memory (stbi_uc const *buffer, int len) +{ + stbi__context s; + stbi__start_mem(&s,buffer, len); + return stbi__dds_test(&s); +} + +int stbi__dds_test_callbacks (stbi_io_callbacks const *clbk, void *user) { - stbi s; - start_mem(&s,buffer, len); - return dds_test(&s); + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__dds_test(&s); } // helper functions @@ -265,7 +239,104 @@ void stbi_decode_DXT_color_block( } // done } -static stbi_uc *dds_load(stbi *s, int *x, int *y, int *comp, int req_comp) + +static int stbi__dds_info( stbi__context *s, int *x, int *y, int *comp, int *iscompressed ) { + int flags,is_compressed,has_alpha; + DDS_header header={0}; + + if( sizeof( DDS_header ) != 128 ) + { + return 0; + } + + stbi__getn( s, (stbi_uc*)(&header), 128 ); + + if( header.dwMagic != (('D' << 0) | ('D' << 8) | ('S' << 16) | (' ' << 24)) ) { + stbi__rewind( s ); + return 0; + } + if( header.dwSize != 124 ) { + stbi__rewind( s ); + return 0; + } + flags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT; + if( (header.dwFlags & flags) != flags ) { + stbi__rewind( s ); + return 0; + } + if( header.sPixelFormat.dwSize != 32 ) { + stbi__rewind( s ); + return 0; + } + flags = DDPF_FOURCC | DDPF_RGB; + if( (header.sPixelFormat.dwFlags & flags) == 0 ) { + stbi__rewind( s ); + return 0; + } + if( (header.sCaps.dwCaps1 & DDSCAPS_TEXTURE) == 0 ) { + stbi__rewind( s ); + return 0; + } + + is_compressed = (header.sPixelFormat.dwFlags & DDPF_FOURCC) / DDPF_FOURCC; + has_alpha = (header.sPixelFormat.dwFlags & DDPF_ALPHAPIXELS) / DDPF_ALPHAPIXELS; + + *x = header.dwWidth; + *y = header.dwHeight; + + if ( !is_compressed ) { + *comp = 3; + + if ( has_alpha ) + *comp = 4; + } + else + *comp = 4; + + if ( iscompressed ) + *iscompressed = is_compressed; + + return 1; +} + +int stbi__dds_info_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int *iscompressed) +{ + stbi__context s; + stbi__start_mem(&s,buffer, len); + return stbi__dds_info( &s, x, y, comp, iscompressed ); +} + +int stbi__dds_info_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int *iscompressed) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__dds_info( &s, x, y, comp, iscompressed ); +} + +#ifndef STBI_NO_STDIO +int stbi__dds_info_from_path(char const *filename, int *x, int *y, int *comp, int *iscompressed) +{ + int res; + FILE *f = fopen(filename, "rb"); + if (!f) return 0; + res = stbi__dds_info_from_file( f, x, y, comp, iscompressed ); + fclose(f); + return res; +} + +int stbi__dds_info_from_file(FILE *f, int *x, int *y, int *comp, int *iscompressed) +{ + stbi__context s; + int res; + long n = ftell(f); + stbi__start_file(&s, f); + res = stbi__dds_info(&s, x, y, comp, iscompressed); + fseek(f, n, SEEK_SET); + return res; +} +#endif + +static void * stbi__dds_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) { // all variables go up front stbi_uc *dds_data = NULL; @@ -275,14 +346,14 @@ static stbi_uc *dds_load(stbi *s, int *x, int *y, int *comp, int req_comp) int has_alpha, has_mipmap; int is_compressed, cubemap_faces; int block_pitch, num_blocks; - DDS_header header; + DDS_header header={0}; int i, sz, cf; // load the header if( sizeof( DDS_header ) != 128 ) { return NULL; } - getn( s, (stbi_uc*)(&header), 128 ); + stbi__getn( s, (stbi_uc*)(&header), 128 ); // and do some checking if( header.dwMagic != (('D' << 0) | ('D' << 8) | ('S' << 16) | (' ' << 24)) ) return NULL; if( header.dwSize != 124 ) return NULL; @@ -341,29 +412,29 @@ static stbi_uc *dds_load(stbi *s, int *x, int *y, int *comp, int req_comp) if( DXT_family == 1 ) { // DXT1 - getn( s, compressed, 8 ); + stbi__getn( s, compressed, 8 ); stbi_decode_DXT1_block( block, compressed ); } else if( DXT_family < 4 ) { // DXT2/3 - getn( s, compressed, 8 ); + stbi__getn( s, compressed, 8 ); stbi_decode_DXT23_alpha_block ( block, compressed ); - getn( s, compressed, 8 ); + stbi__getn( s, compressed, 8 ); stbi_decode_DXT_color_block ( block, compressed ); } else { // DXT4/5 - getn( s, compressed, 8 ); + stbi__getn( s, compressed, 8 ); stbi_decode_DXT45_alpha_block ( block, compressed ); - getn( s, compressed, 8 ); + stbi__getn( s, compressed, 8 ); stbi_decode_DXT_color_block ( block, compressed ); } // is this a partial block? - if( ref_x + 4 > s->img_x ) + if( ref_x + 4 > (int)s->img_x ) { bw = s->img_x - ref_x; } - if( ref_y + 4 > s->img_y ) + if( ref_y + 4 > (int)s->img_y ) { bh = s->img_y - ref_y; } @@ -379,7 +450,7 @@ static stbi_uc *dds_load(stbi *s, int *x, int *y, int *comp, int req_comp) } } /* done reading and decoding the main image... - skip MIPmaps if present */ + stbi__skip MIPmaps if present */ if( has_mipmap ) { int block_size = 16; @@ -387,7 +458,7 @@ static stbi_uc *dds_load(stbi *s, int *x, int *y, int *comp, int req_comp) { block_size = 8; } - for( i = 1; i < header.dwMipMapCount; ++i ) + for( i = 1; i < (int)header.dwMipMapCount; ++i ) { int mx = s->img_x >> (i + 2); int my = s->img_y >> (i + 2); @@ -399,7 +470,7 @@ static stbi_uc *dds_load(stbi *s, int *x, int *y, int *comp, int req_comp) { my = 1; } - skip( s, mx*my*block_size ); + stbi__skip( s, mx*my*block_size ); } } }/* per cubemap face */ @@ -419,12 +490,12 @@ static stbi_uc *dds_load(stbi *s, int *x, int *y, int *comp, int req_comp) for( cf = 0; cf < cubemap_faces; ++ cf ) { /* read the main image for this face */ - getn( s, &dds_data[cf*s->img_x*s->img_y*s->img_n], s->img_x*s->img_y*s->img_n ); + stbi__getn( s, &dds_data[cf*s->img_x*s->img_y*s->img_n], s->img_x*s->img_y*s->img_n ); /* done reading and decoding the main image... - skip MIPmaps if present */ + stbi__skip MIPmaps if present */ if( has_mipmap ) { - for( i = 1; i < header.dwMipMapCount; ++i ) + for( i = 1; i < (int)header.dwMipMapCount; ++i ) { int mx = s->img_x >> i; int my = s->img_y >> i; @@ -436,7 +507,7 @@ static stbi_uc *dds_load(stbi *s, int *x, int *y, int *comp, int req_comp) { my = 1; } - skip( s, mx*my*s->img_n ); + stbi__skip( s, mx*my*s->img_n ); } } } @@ -468,15 +539,15 @@ static stbi_uc *dds_load(stbi *s, int *x, int *y, int *comp, int req_comp) // user has some requirements, meet them if( req_comp != s->img_n ) { - dds_data = convert_format( dds_data, s->img_n, req_comp, s->img_x, s->img_y ); - *comp = s->img_n; + dds_data = stbi__convert_format( dds_data, s->img_n, req_comp, s->img_x, s->img_y ); + *comp = req_comp; } } else { // user had no requirements, only drop to RGB is no alpha if( (has_alpha == 0) && (s->img_n == 4) ) { - dds_data = convert_format( dds_data, 4, 3, s->img_x, s->img_y ); + dds_data = stbi__convert_format( dds_data, 4, 3, s->img_x, s->img_y ); *comp = 3; } } @@ -485,27 +556,34 @@ static stbi_uc *dds_load(stbi *s, int *x, int *y, int *comp, int req_comp) } #ifndef STBI_NO_STDIO -stbi_uc *stbi_dds_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp) +void *stbi__dds_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp) { - stbi s; - start_file(&s,f); - return dds_load(&s,x,y,comp,req_comp); + stbi__context s; + stbi__start_file(&s,f); + return stbi__dds_load(&s,x,y,comp,req_comp); } -stbi_uc *stbi_dds_load (char *filename, int *x, int *y, int *comp, int req_comp) +void *stbi__dds_load_from_path (const char *filename, int *x, int *y, int *comp, int req_comp) { - stbi_uc *data; + void *data; FILE *f = fopen(filename, "rb"); if (!f) return NULL; - data = stbi_dds_load_from_file(f,x,y,comp,req_comp); + data = stbi__dds_load_from_file(f,x,y,comp,req_comp); fclose(f); return data; } #endif -stbi_uc *stbi_dds_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +void *stbi__dds_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer, len); + return stbi__dds_load(&s,x,y,comp,req_comp); +} + +void *stbi__dds_load_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) { - stbi s; - start_mem(&s,buffer, len); - return dds_load(&s,x,y,comp,req_comp); + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__dds_load(&s,x,y,comp,req_comp); } diff --git a/lib/SOIL2/stbi_ext.h b/lib/SOIL2/stbi_ext.h new file mode 100644 index 000000000..de0ecc879 --- /dev/null +++ b/lib/SOIL2/stbi_ext.h @@ -0,0 +1,28 @@ +#ifndef HEADER_STB_IMAGE_EXT +#define HEADER_STB_IMAGE_EXT + +enum { + STBI_unknown= 0, + STBI_jpeg = 1, + STBI_png = 2, + STBI_bmp = 3, + STBI_gif = 4, + STBI_tga = 5, + STBI_psd = 6, + STBI_pic = 7, + STBI_pnm = 8, + STBI_dds = 9, + STBI_pvr = 10, + STBI_pkm = 11, + STBI_hdr = 12 +}; + +extern int stbi_test_from_memory (stbi_uc const *buffer, int len); +extern int stbi_test_from_callbacks (stbi_io_callbacks const *clbk, void *user); + +#ifndef STBI_NO_STDIO +extern int stbi_test (char const *filename); +extern int stbi_test_from_file (FILE *f); +#endif + +#endif /* HEADER_STB_IMAGE_EXT */ diff --git a/lib/SOIL2/stbi_ext_c.h b/lib/SOIL2/stbi_ext_c.h new file mode 100644 index 000000000..224f436bc --- /dev/null +++ b/lib/SOIL2/stbi_ext_c.h @@ -0,0 +1,74 @@ + +static int stbi_test_main(stbi__context *s) +{ + #ifndef STBI_NO_JPEG + if (stbi__jpeg_test(s)) return STBI_jpeg; + #endif + #ifndef STBI_NO_PNG + if (stbi__png_test(s)) return STBI_png; + #endif + #ifndef STBI_NO_BMP + if (stbi__bmp_test(s)) return STBI_bmp; + #endif + #ifndef STBI_NO_GIF + if (stbi__gif_test(s)) return STBI_gif; + #endif + #ifndef STBI_NO_PSD + if (stbi__psd_test(s)) return STBI_psd; + #endif + #ifndef STBI_NO_PIC + if (stbi__pic_test(s)) return STBI_pic; + #endif + #ifndef STBI_NO_PNM + if (stbi__pnm_test(s)) return STBI_pnm; + #endif + #ifndef STBI_NO_DDS + if (stbi__dds_test(s)) return STBI_dds; + #endif + #ifndef STBI_NO_PVR + if (stbi__pvr_test(s)) return STBI_pvr; + #endif + #ifndef STBI_NO_PKM + if (stbi__pkm_test(s)) return STBI_pkm; + #endif + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) return STBI_hdr; + #endif + #ifndef STBI_NO_TGA + if (stbi__tga_test(s)) return STBI_tga; + #endif + return STBI_unknown; +} + +#ifndef STBI_NO_STDIO +int stbi_test_from_file(FILE *f) +{ + stbi__context s; + stbi__start_file(&s,f); + return stbi_test_main(&s); +} + +int stbi_test(char const *filename) +{ + FILE *f = fopen(filename, "rb"); + int result; + if (!f) return STBI_unknown; + result = stbi_test_from_file(f); + fclose(f); + return result; +} +#endif //!STBI_NO_STDIO + +int stbi_test_from_memory(stbi_uc const *buffer, int len) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi_test_main(&s); +} + +int stbi_test_from_callbacks(stbi_io_callbacks const *clbk, void *user) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi_test_main(&s); +} diff --git a/lib/SOIL2/stbi_pkm.h b/lib/SOIL2/stbi_pkm.h new file mode 100644 index 000000000..92f064080 --- /dev/null +++ b/lib/SOIL2/stbi_pkm.h @@ -0,0 +1,34 @@ +/* + adding PKM loading support to stbi +*/ + +#ifndef HEADER_STB_IMAGE_PKM_AUGMENTATION +#define HEADER_STB_IMAGE_PKM_AUGMENTATION + +/* is it a PKM file? */ +extern int stbi__pkm_test_memory (stbi_uc const *buffer, int len); +extern int stbi__pkm_test_callbacks (stbi_io_callbacks const *clbk, void *user); + +extern void *stbi__pkm_load_from_path (char const *filename, int *x, int *y, int *comp, int req_comp); +extern void *stbi__pkm_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); +extern void *stbi__pkm_load_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp); + +#ifndef STBI_NO_STDIO +extern int stbi__pkm_test_filename (char const *filename); +extern int stbi__pkm_test_file (FILE *f); +extern void *stbi__pkm_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +#endif + +extern int stbi__pkm_info_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp); +extern int stbi__pkm_info_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); + + +#ifndef STBI_NO_STDIO +extern int stbi__pkm_info_from_path (char const *filename, int *x, int *y, int *comp); +extern int stbi__pkm_info_from_file (FILE *f, int *x, int *y, int *comp); +#endif + +/* +// +//// end header file /////////////////////////////////////////////////////*/ +#endif /* HEADER_STB_IMAGE_PKM_AUGMENTATION */ diff --git a/lib/SOIL2/stbi_pkm_c.h b/lib/SOIL2/stbi_pkm_c.h new file mode 100644 index 000000000..a05182cb1 --- /dev/null +++ b/lib/SOIL2/stbi_pkm_c.h @@ -0,0 +1,227 @@ +#include "pkm_helper.h" +#include "etc1_utils.h" + +static int stbi__pkm_test(stbi__context *s) +{ + // check the magic number + if (stbi__get8(s) != 'P') { + stbi__rewind(s); + return 0; + } + + if (stbi__get8(s) != 'K') { + stbi__rewind(s); + return 0; + } + + if (stbi__get8(s) != 'M') { + stbi__rewind(s); + return 0; + } + + if (stbi__get8(s) != ' ') { + stbi__rewind(s); + return 0; + } + + if (stbi__get8(s) != '1') { + stbi__rewind(s); + return 0; + } + + if (stbi__get8(s) != '0') { + stbi__rewind(s); + return 0; + } + + stbi__rewind(s); + return 1; +} + +#ifndef STBI_NO_STDIO + +int stbi__pkm_test_filename (char const *filename) +{ + int r; + FILE *f = fopen(filename, "rb"); + if (!f) return 0; + r = stbi__pkm_test_file(f); + fclose(f); + return r; +} + +int stbi__pkm_test_file (FILE *f) +{ + stbi__context s; + int r,n = ftell(f); + stbi__start_file(&s,f); + r = stbi__pkm_test(&s); + fseek(f,n,SEEK_SET); + return r; +} +#endif + +int stbi__pkm_test_memory (stbi_uc const *buffer, int len) +{ + stbi__context s; + stbi__start_mem(&s,buffer, len); + return stbi__pkm_test(&s); +} + +int stbi__pkm_test_callbacks (stbi_io_callbacks const *clbk, void *user) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__pkm_test(&s); +} + +static int stbi__pkm_info(stbi__context *s, int *x, int *y, int *comp ) +{ + PKMHeader header; + unsigned int width, height; + + stbi__getn( s, (stbi_uc*)(&header), sizeof(PKMHeader) ); + + if ( 0 != strcmp( header.aName, "PKM 10" ) ) { + stbi__rewind(s); + return 0; + } + + width = (header.iWidthMSB << 8) | header.iWidthLSB; + height = (header.iHeightMSB << 8) | header.iHeightLSB; + + *x = s->img_x = width; + *y = s->img_y = height; + *comp = s->img_n = 3; + + stbi__rewind(s); + + return 1; +} + +int stbi__pkm_info_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp ) +{ + stbi__context s; + stbi__start_mem(&s,buffer, len); + return stbi__pkm_info( &s, x, y, comp ); +} + +int stbi__pkm_info_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__pkm_info( &s, x, y, comp ); +} + +#ifndef STBI_NO_STDIO +int stbi__pkm_info_from_path(char const *filename, int *x, int *y, int *comp) +{ + int res; + FILE *f = fopen(filename, "rb"); + if (!f) return 0; + res = stbi__pkm_info_from_file( f, x, y, comp ); + fclose(f); + return res; +} + +int stbi__pkm_info_from_file(FILE *f, int *x, int *y, int *comp) +{ + stbi__context s; + int res; + long n = ftell(f); + stbi__start_file(&s, f); + res = stbi__pkm_info(&s, x, y, comp); + fseek(f, n, SEEK_SET); + return res; +} +#endif + +static void * stbi__pkm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi_uc *pkm_data = NULL; + stbi_uc *pkm_res_data = NULL; + PKMHeader header; + unsigned int width; + unsigned int height; + unsigned int align = 0; + unsigned int bpr; + unsigned int size; + unsigned int compressedSize; + + int res; + + stbi__getn( s, (stbi_uc*)(&header), sizeof(PKMHeader) ); + + if ( 0 != strcmp( header.aName, "PKM 10" ) ) { + return NULL; + } + + width = (header.iWidthMSB << 8) | header.iWidthLSB; + height = (header.iHeightMSB << 8) | header.iHeightLSB; + + *x = s->img_x = width; + *y = s->img_y = height; + *comp = s->img_n = 3; + + compressedSize = etc1_get_encoded_data_size(width, height); + + pkm_data = (stbi_uc *)malloc(compressedSize); + stbi__getn( s, pkm_data, compressedSize ); + + bpr = ((width * 3) + align) & ~align; + size = bpr * height; + pkm_res_data = (stbi_uc *)malloc(size); + + res = etc1_decode_image((const etc1_byte*)pkm_data, (etc1_byte*)pkm_res_data, width, height, 3, bpr); + + free( pkm_data ); + + if ( 0 == res ) { + if( (req_comp <= 4) && (req_comp >= 1) ) { + // user has some requirements, meet them + if( req_comp != s->img_n ) { + pkm_res_data = stbi__convert_format( pkm_res_data, s->img_n, req_comp, s->img_x, s->img_y ); + *comp = req_comp; + } + } + + return (stbi_uc *)pkm_res_data; + } else { + free( pkm_res_data ); + } + + return NULL; +} + +#ifndef STBI_NO_STDIO +void *stbi__pkm_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_file(&s,f); + return stbi__pkm_load(&s,x,y,comp,req_comp); +} + +void *stbi__pkm_load_from_path (char const*filename, int *x, int *y, int *comp, int req_comp) +{ + void *data; + FILE *f = fopen(filename, "rb"); + if (!f) return NULL; + data = stbi__pkm_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return data; +} +#endif + +void *stbi__pkm_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer, len); + return stbi__pkm_load(&s,x,y,comp,req_comp); +} + +void *stbi__pkm_load_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__pkm_load(&s,x,y,comp,req_comp); +} diff --git a/lib/SOIL2/stbi_pvr.h b/lib/SOIL2/stbi_pvr.h new file mode 100644 index 000000000..e982715a8 --- /dev/null +++ b/lib/SOIL2/stbi_pvr.h @@ -0,0 +1,34 @@ +/* + adding PVR loading support to stbi +*/ + +#ifndef HEADER_STB_IMAGE_PVR_AUGMENTATION +#define HEADER_STB_IMAGE_PVR_AUGMENTATION + +/* is it a PVR file? */ +extern int stbi__pvr_test_memory (stbi_uc const *buffer, int len); +extern int stbi__pvr_test_callbacks (stbi_io_callbacks const *clbk, void *user); + +extern void *stbi__pvr_load_from_path (char const *filename, int *x, int *y, int *comp, int req_comp); +extern void *stbi__pvr_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); +extern void *stbi__pvr_load_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp); + +#ifndef STBI_NO_STDIO +extern int stbi__pvr_test_filename (char const *filename); +extern int stbi__pvr_test_file (FILE *f); +extern void *stbi__pvr_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +#endif + +extern int stbi__pvr_info_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int *iscompressed); +extern int stbi__pvr_info_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int *iscompressed); + + +#ifndef STBI_NO_STDIO +extern int stbi__pvr_info_from_path (char const *filename, int *x, int *y, int *comp, int *iscompressed); +extern int stbi__pvr_info_from_file (FILE *f, int *x, int *y, int *comp, int *iscompressed); +#endif + +/* +// +//// end header file /////////////////////////////////////////////////////*/ +#endif /* HEADER_STB_IMAGE_PVR_AUGMENTATION */ diff --git a/lib/SOIL2/stbi_pvr_c.h b/lib/SOIL2/stbi_pvr_c.h new file mode 100644 index 000000000..991746526 --- /dev/null +++ b/lib/SOIL2/stbi_pvr_c.h @@ -0,0 +1,1001 @@ +#include "pvr_helper.h" + +static int stbi__pvr_test(stbi__context *s) +{ + // check header size + if (stbi__get32le(s) != sizeof(PVR_Texture_Header)) { + stbi__rewind(s); + return 0; + } + + // stbi__skip until the magic number + stbi__skip(s, 10*4); + + // check the magic number + if ( stbi__get32le(s) != PVRTEX_IDENTIFIER ) { + stbi__rewind(s); + return 0; + } + + // Also rewind because the loader needs to read the header + stbi__rewind(s); + + return 1; +} + +#ifndef STBI_NO_STDIO + +int stbi__pvr_test_filename (char const *filename) +{ + int r; + FILE *f = fopen(filename, "rb"); + if (!f) return 0; + r = stbi__pvr_test_file(f); + fclose(f); + return r; +} + +int stbi__pvr_test_file (FILE *f) +{ + stbi__context s; + int r,n = ftell(f); + stbi__start_file(&s,f); + r = stbi__pvr_test(&s); + fseek(f,n,SEEK_SET); + return r; +} +#endif + +int stbi__pvr_test_memory (stbi_uc const *buffer, int len) +{ + stbi__context s; + stbi__start_mem(&s,buffer, len); + return stbi__pvr_test(&s); +} + +int stbi__pvr_test_callbacks (stbi_io_callbacks const *clbk, void *user) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__pvr_test(&s); +} + +static int stbi__pvr_info(stbi__context *s, int *x, int *y, int *comp, int * iscompressed ) +{ + PVR_Texture_Header header={0}; + + stbi__getn( s, (stbi_uc*)(&header), sizeof(PVR_Texture_Header) ); + + // Check the header size + if ( header.dwHeaderSize != sizeof(PVR_Texture_Header) ) { + stbi__rewind( s ); + return 0; + } + + // Check the magic identifier + if ( header.dwPVR != PVRTEX_IDENTIFIER ) { + stbi__rewind(s); + return 0; + } + + *x = s->img_x = header.dwWidth; + *y = s->img_y = header.dwHeight; + *comp = s->img_n = ( header.dwBitCount + 7 ) / 8; + + if ( iscompressed ) + *iscompressed = 0; + + switch ( header.dwpfFlags & PVRTEX_PIXELTYPE ) + { + case OGL_RGBA_4444: + s->img_n = 2; + break; + case OGL_RGBA_5551: + s->img_n = 2; + break; + case OGL_RGBA_8888: + s->img_n = 4; + break; + case OGL_RGB_565: + s->img_n = 2; + break; + case OGL_RGB_888: + s->img_n = 3; + break; + case OGL_I_8: + s->img_n = 1; + break; + case OGL_AI_88: + s->img_n = 2; + break; + case OGL_PVRTC2: + s->img_n = 4; + if ( iscompressed ) + *iscompressed = 1; + break; + case OGL_PVRTC4: + s->img_n = 4; + if ( iscompressed ) + *iscompressed = 1; + break; + case OGL_RGB_555: + default: + stbi__rewind(s); + return 0; + } + + *comp = s->img_n; + + return 1; +} + +int stbi__pvr_info_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int * iscompressed ) +{ + stbi__context s; + stbi__start_mem(&s,buffer, len); + return stbi__pvr_info( &s, x, y, comp, iscompressed ); +} + +int stbi__pvr_info_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int * iscompressed) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__pvr_info( &s, x, y, comp, iscompressed ); +} + +#ifndef STBI_NO_STDIO +int stbi__pvr_info_from_path(char const *filename, int *x, int *y, int *comp, int * iscompressed) +{ + int res; + FILE *f = fopen(filename, "rb"); + if (!f) return 0; + res = stbi__pvr_info_from_file( f, x, y, comp, iscompressed ); + fclose(f); + return res; +} + +int stbi__pvr_info_from_file(FILE *f, int *x, int *y, int *comp, int * iscompressed) +{ + stbi__context s; + int res; + long n = ftell(f); + stbi__start_file(&s, f); + res = stbi__pvr_info(&s, x, y, comp, iscompressed); + fseek(f, n, SEEK_SET); + return res; +} +#endif + +/****************************************************************************** + Taken from: + @File PVRTDecompress.cpp + @Title PVRTDecompress + @Copyright Copyright (C) Imagination Technologies Limited. + @Platform ANSI compatible + @Description PVRTC Texture Decompression. +******************************************************************************/ + +typedef unsigned char PVRTuint8; +typedef unsigned short PVRTuint16; +typedef unsigned int PVRTuint32; + +/***************************************************************************** + * defines and consts + *****************************************************************************/ +#define PT_INDEX (2) // The Punch-through index + +#define BLK_Y_SIZE (4) // always 4 for all 2D block types + +#define BLK_X_MAX (8) // Max X dimension for blocks + +#define BLK_X_2BPP (8) // dimensions for the two formats +#define BLK_X_4BPP (4) + +#define WRAP_COORD(Val, Size) ((Val) & ((Size)-1)) + +#define POWER_OF_2(X) util_number_is_power_2(X) + +/* + Define an expression to either wrap or clamp large or small vals to the + legal coordinate range +*/ +#define PVRT_MIN(a,b) (((a) < (b)) ? (a) : (b)) +#define PVRT_MAX(a,b) (((a) > (b)) ? (a) : (b)) +#define PVRT_CLAMP(x, l, h) (PVRT_MIN((h), PVRT_MAX((x), (l)))) + +#define LIMIT_COORD(Val, Size, AssumeImageTiles) \ + ((AssumeImageTiles)? WRAP_COORD((Val), (Size)): PVRT_CLAMP((Val), 0, (Size)-1)) + +/***************************************************************************** + * Useful typedefs + *****************************************************************************/ +typedef PVRTuint32 U32; +typedef PVRTuint8 U8; + +/*********************************************************** + DECOMPRESSION ROUTINES +************************************************************/ + +/*!*********************************************************************** + @Struct AMTC_BLOCK_STRUCT + @Brief +*************************************************************************/ +typedef struct +{ + // Uses 64 bits pre block + U32 PackedData[2]; +}AMTC_BLOCK_STRUCT; + + /*!*********************************************************************** + @Function util_number_is_power_2 + @Input input A number + @Returns TRUE if the number is an integer power of two, else FALSE. + @Description Check that a number is an integer power of two, i.e. + 1, 2, 4, 8, ... etc. + Returns FALSE for zero. +*************************************************************************/ +int util_number_is_power_2( unsigned input ) +{ + unsigned minus1; + + if( !input ) return 0; + + minus1 = input - 1; + return ( (input | minus1) == (input ^ minus1) ) ? 1 : 0; +} + +/*!*********************************************************************** + @Function Unpack5554Colour + @Input pBlock + @Input ABColours + @Description Given a block, extract the colour information and convert + to 5554 formats +*************************************************************************/ +static void Unpack5554Colour(const AMTC_BLOCK_STRUCT *pBlock, + int ABColours[2][4]) +{ + U32 RawBits[2]; + + int i; + + // Extract A and B + RawBits[0] = pBlock->PackedData[1] & (0xFFFE); // 15 bits (shifted up by one) + RawBits[1] = pBlock->PackedData[1] >> 16; // 16 bits + + // step through both colours + for(i = 0; i < 2; i++) + { + // If completely opaque + if(RawBits[i] & (1<<15)) + { + // Extract R and G (both 5 bit) + ABColours[i][0] = (RawBits[i] >> 10) & 0x1F; + ABColours[i][1] = (RawBits[i] >> 5) & 0x1F; + + /* + The precision of Blue depends on A or B. If A then we need to + replicate the top bit to get 5 bits in total + */ + ABColours[i][2] = RawBits[i] & 0x1F; + if(i==0) + { + ABColours[0][2] |= ABColours[0][2] >> 4; + } + + // set 4bit alpha fully on... + ABColours[i][3] = 0xF; + } + else // Else if colour has variable translucency + { + /* + Extract R and G (both 4 bit). + (Leave a space on the end for the replication of bits + */ + ABColours[i][0] = (RawBits[i] >> (8-1)) & 0x1E; + ABColours[i][1] = (RawBits[i] >> (4-1)) & 0x1E; + + // replicate bits to truly expand to 5 bits + ABColours[i][0] |= ABColours[i][0] >> 4; + ABColours[i][1] |= ABColours[i][1] >> 4; + + // grab the 3(+padding) or 4 bits of blue and add an extra padding bit + ABColours[i][2] = (RawBits[i] & 0xF) << 1; + + /* + expand from 3 to 5 bits if this is from colour A, or 4 to 5 bits if from + colour B + */ + if(i==0) + { + ABColours[0][2] |= ABColours[0][2] >> 3; + } + else + { + ABColours[0][2] |= ABColours[0][2] >> 4; + } + + // Set the alpha bits to be 3 + a zero on the end + ABColours[i][3] = (RawBits[i] >> 11) & 0xE; + } + } +} + +/*!*********************************************************************** + @Function UnpackModulations + @Input pBlock + @Input Do2bitMode + @Input ModulationVals + @Input ModulationModes + @Input StartX + @Input StartY + @Description Given the block and the texture type and it's relative + position in the 2x2 group of blocks, extract the bit + patterns for the fully defined pixels. +*************************************************************************/ +static void UnpackModulations(const AMTC_BLOCK_STRUCT *pBlock, + const int Do2bitMode, + int ModulationVals[8][16], + int ModulationModes[8][16], + int StartX, + int StartY) +{ + int BlockModMode; + U32 ModulationBits; + + int x, y; + + BlockModMode= pBlock->PackedData[1] & 1; + ModulationBits = pBlock->PackedData[0]; + + // if it's in an interpolated mode + if(Do2bitMode && BlockModMode) + { + /* + run through all the pixels in the block. Note we can now treat all the + "stored" values as if they have 2bits (even when they didn't!) + */ + for(y = 0; y < BLK_Y_SIZE; y++) + { + for(x = 0; x < BLK_X_2BPP; x++) + { + ModulationModes[y+StartY][x+StartX] = BlockModMode; + + // if this is a stored value... + if(((x^y)&1) == 0) + { + ModulationVals[y+StartY][x+StartX] = ModulationBits & 3; + ModulationBits >>= 2; + } + } + } + } + else if(Do2bitMode) // else if direct encoded 2bit mode - i.e. 1 mode bit per pixel + { + for(y = 0; y < BLK_Y_SIZE; y++) + { + for(x = 0; x < BLK_X_2BPP; x++) + { + ModulationModes[y+StartY][x+StartX] = BlockModMode; + + // double the bits so 0=> 00, and 1=>11 + if(ModulationBits & 1) + { + ModulationVals[y+StartY][x+StartX] = 0x3; + } + else + { + ModulationVals[y+StartY][x+StartX] = 0x0; + } + ModulationBits >>= 1; + } + } + } + else // else its the 4bpp mode so each value has 2 bits + { + for(y = 0; y < BLK_Y_SIZE; y++) + { + for(x = 0; x < BLK_X_4BPP; x++) + { + ModulationModes[y+StartY][x+StartX] = BlockModMode; + + ModulationVals[y+StartY][x+StartX] = ModulationBits & 3; + ModulationBits >>= 2; + } + } + } + + // make sure nothing is left over + assert(ModulationBits==0); +} + +/*!*********************************************************************** + @Function InterpolateColours + @Input ColourP + @Input ColourQ + @Input ColourR + @Input ColourS + @Input Do2bitMode + @Input x + @Input y + @Modified Result + @Description This performs a HW bit accurate interpolation of either the + A or B colours for a particular pixel. + + NOTE: It is assumed that the source colours are in ARGB 5554 + format - This means that some "preparation" of the values will + be necessary. +*************************************************************************/ +static void InterpolateColours(const int ColourP[4], + const int ColourQ[4], + const int ColourR[4], + const int ColourS[4], + const int Do2bitMode, + const int x, + const int y, + int Result[4]) +{ + int u, v, uscale; + int k; + + int tmp1, tmp2; + + int P[4], Q[4], R[4], S[4]; + + // Copy the colours + for(k = 0; k < 4; k++) + { + P[k] = ColourP[k]; + Q[k] = ColourQ[k]; + R[k] = ColourR[k]; + S[k] = ColourS[k]; + } + + // put the x and y values into the right range + v = (y & 0x3) | ((~y & 0x2) << 1); + + if(Do2bitMode) + u = (x & 0x7) | ((~x & 0x4) << 1); + else + u = (x & 0x3) | ((~x & 0x2) << 1); + + // get the u and v scale amounts + v = v - BLK_Y_SIZE/2; + + if(Do2bitMode) + { + u = u - BLK_X_2BPP/2; + uscale = 8; + } + else + { + u = u - BLK_X_4BPP/2; + uscale = 4; + } + + for(k = 0; k < 4; k++) + { + tmp1 = P[k] * uscale + u * (Q[k] - P[k]); + tmp2 = R[k] * uscale + u * (S[k] - R[k]); + + tmp1 = tmp1 * 4 + v * (tmp2 - tmp1); + + Result[k] = tmp1; + } + + // Lop off the appropriate number of bits to get us to 8 bit precision + if(Do2bitMode) + { + // do RGB + for(k = 0; k < 3; k++) + { + Result[k] >>= 2; + } + + Result[3] >>= 1; + } + else + { + // do RGB (A is ok) + for(k = 0; k < 3; k++) + { + Result[k] >>= 1; + } + } + + // sanity check + for(k = 0; k < 4; k++) + { + assert(Result[k] < 256); + } + + + /* + Convert from 5554 to 8888 + + do RGB 5.3 => 8 + */ + for(k = 0; k < 3; k++) + { + Result[k] += Result[k] >> 5; + } + + Result[3] += Result[3] >> 4; + + // 2nd sanity check + for(k = 0; k < 4; k++) + { + assert(Result[k] < 256); + } + +} + +/*!*********************************************************************** + @Function GetModulationValue + @Input x + @Input y + @Input Do2bitMode + @Input ModulationVals + @Input ModulationModes + @Input Mod + @Input DoPT + @Description Get the modulation value as a numerator of a fraction of 8ths +*************************************************************************/ +static void GetModulationValue(int x, + int y, + const int Do2bitMode, + const int ModulationVals[8][16], + const int ModulationModes[8][16], + int *Mod, + int *DoPT) +{ + static const int RepVals0[4] = {0, 3, 5, 8}; + static const int RepVals1[4] = {0, 4, 4, 8}; + + int ModVal; + + // Map X and Y into the local 2x2 block + y = (y & 0x3) | ((~y & 0x2) << 1); + + if(Do2bitMode) + x = (x & 0x7) | ((~x & 0x4) << 1); + else + x = (x & 0x3) | ((~x & 0x2) << 1); + + // assume no PT for now + *DoPT = 0; + + // extract the modulation value. If a simple encoding + if(ModulationModes[y][x]==0) + { + ModVal = RepVals0[ModulationVals[y][x]]; + } + else if(Do2bitMode) + { + // if this is a stored value + if(((x^y)&1)==0) + ModVal = RepVals0[ModulationVals[y][x]]; + else if(ModulationModes[y][x] == 1) // else average from the neighbours if H&V interpolation.. + { + ModVal = (RepVals0[ModulationVals[y-1][x]] + + RepVals0[ModulationVals[y+1][x]] + + RepVals0[ModulationVals[y][x-1]] + + RepVals0[ModulationVals[y][x+1]] + 2) / 4; + } + else if(ModulationModes[y][x] == 2) // else if H-Only + { + ModVal = (RepVals0[ModulationVals[y][x-1]] + + RepVals0[ModulationVals[y][x+1]] + 1) / 2; + } + else // else it's V-Only + { + ModVal = (RepVals0[ModulationVals[y-1][x]] + + RepVals0[ModulationVals[y+1][x]] + 1) / 2; + } + } + else // else it's 4BPP and PT encoding + { + ModVal = RepVals1[ModulationVals[y][x]]; + + *DoPT = ModulationVals[y][x] == PT_INDEX; + } + + *Mod =ModVal; +} + +/*!*********************************************************************** + @Function TwiddleUV + @Input YSize Y dimension of the texture in pixels + @Input XSize X dimension of the texture in pixels + @Input YPos Pixel Y position + @Input XPos Pixel X position + @Returns The twiddled offset of the pixel + @Description Given the Block (or pixel) coordinates and the dimension of + the texture in blocks (or pixels) this returns the twiddled + offset of the block (or pixel) from the start of the map. + + NOTE the dimensions of the texture must be a power of 2 +*************************************************************************/ +static int DisableTwiddlingRoutine = 0; + +static U32 TwiddleUV(U32 YSize, U32 XSize, U32 YPos, U32 XPos) +{ + U32 Twiddled; + + U32 MinDimension; + U32 MaxValue; + + U32 SrcBitPos; + U32 DstBitPos; + + int ShiftCount; + + assert(YPos < YSize); + assert(XPos < XSize); + + assert(POWER_OF_2(YSize)); + assert(POWER_OF_2(XSize)); + + if(YSize < XSize) + { + MinDimension = YSize; + MaxValue = XPos; + } + else + { + MinDimension = XSize; + MaxValue = YPos; + } + + // Nasty hack to disable twiddling + if(DisableTwiddlingRoutine) + return (YPos* XSize + XPos); + + // Step through all the bits in the "minimum" dimension + SrcBitPos = 1; + DstBitPos = 1; + Twiddled = 0; + ShiftCount = 0; + + while(SrcBitPos < MinDimension) + { + if(YPos & SrcBitPos) + { + Twiddled |= DstBitPos; + } + + if(XPos & SrcBitPos) + { + Twiddled |= (DstBitPos << 1); + } + + + SrcBitPos <<= 1; + DstBitPos <<= 2; + ShiftCount += 1; + + } + + // prepend any unused bits + MaxValue >>= ShiftCount; + + Twiddled |= (MaxValue << (2*ShiftCount)); + + return Twiddled; +} + +/***********************************************************/ +/* +// Decompress +// +// Takes the compressed input data and outputs the equivalent decompressed +// image. +*/ +/***********************************************************/ + +static void Decompress(AMTC_BLOCK_STRUCT *pCompressedData, + const int Do2bitMode, + const int XDim, + const int YDim, + const int AssumeImageTiles, + unsigned char* pResultImage) +{ + int x, y; + int i, j; + + int BlkX, BlkY; + int BlkXp1, BlkYp1; + int XBlockSize; + int BlkXDim, BlkYDim; + + int StartX, StartY; + + int ModulationVals[8][16]; + int ModulationModes[8][16]; + + int Mod, DoPT; + + unsigned int uPosition; + + /* + // local neighbourhood of blocks + */ + AMTC_BLOCK_STRUCT *pBlocks[2][2]; + + AMTC_BLOCK_STRUCT *pPrevious[2][2] = {{NULL, NULL}, {NULL, NULL}}; + + /* + // Low precision colours extracted from the blocks + */ + struct + { + int Reps[2][4]; + }Colours5554[2][2]; + + /* + // Interpolated A and B colours for the pixel + */ + int ASig[4], BSig[4]; + + int Result[4]; + + if(Do2bitMode) + { + XBlockSize = BLK_X_2BPP; + } + else + { + XBlockSize = BLK_X_4BPP; + } + + + /* + // For MBX don't allow the sizes to get too small + */ + BlkXDim = PVRT_MAX(2, XDim / XBlockSize); + BlkYDim = PVRT_MAX(2, YDim / BLK_Y_SIZE); + + /* + // Step through the pixels of the image decompressing each one in turn + // + // Note that this is a hideously inefficient way to do this! + */ + for(y = 0; y < YDim; y++) + { + for(x = 0; x < XDim; x++) + { + /* + // map this pixel to the top left neighbourhood of blocks + */ + BlkX = (x - XBlockSize/2); + BlkY = (y - BLK_Y_SIZE/2); + + BlkX = LIMIT_COORD(BlkX, XDim, AssumeImageTiles); + BlkY = LIMIT_COORD(BlkY, YDim, AssumeImageTiles); + + + BlkX /= XBlockSize; + BlkY /= BLK_Y_SIZE; + + //BlkX = LIMIT_COORD(BlkX, BlkXDim, AssumeImageTiles); + //BlkY = LIMIT_COORD(BlkY, BlkYDim, AssumeImageTiles); + + + /* + // compute the positions of the other 3 blocks + */ + BlkXp1 = LIMIT_COORD(BlkX+1, BlkXDim, AssumeImageTiles); + BlkYp1 = LIMIT_COORD(BlkY+1, BlkYDim, AssumeImageTiles); + + /* + // Map to block memory locations + */ + pBlocks[0][0] = pCompressedData +TwiddleUV(BlkYDim, BlkXDim, BlkY, BlkX); + pBlocks[0][1] = pCompressedData +TwiddleUV(BlkYDim, BlkXDim, BlkY, BlkXp1); + pBlocks[1][0] = pCompressedData +TwiddleUV(BlkYDim, BlkXDim, BlkYp1, BlkX); + pBlocks[1][1] = pCompressedData +TwiddleUV(BlkYDim, BlkXDim, BlkYp1, BlkXp1); + + + /* + // extract the colours and the modulation information IF the previous values + // have changed. + */ + if(memcmp(pPrevious, pBlocks, 4*sizeof(void*)) != 0) + { + StartY = 0; + for(i = 0; i < 2; i++) + { + StartX = 0; + for(j = 0; j < 2; j++) + { + Unpack5554Colour(pBlocks[i][j], Colours5554[i][j].Reps); + + UnpackModulations(pBlocks[i][j], + Do2bitMode, + ModulationVals, + ModulationModes, + StartX, StartY); + + StartX += XBlockSize; + }/*end for j*/ + + StartY += BLK_Y_SIZE; + }/*end for i*/ + + /* + // make a copy of the new pointers + */ + memcpy(pPrevious, pBlocks, 4*sizeof(void*)); + }/*end if the blocks have changed*/ + + + /* + // decompress the pixel. First compute the interpolated A and B signals + */ + InterpolateColours(Colours5554[0][0].Reps[0], + Colours5554[0][1].Reps[0], + Colours5554[1][0].Reps[0], + Colours5554[1][1].Reps[0], + Do2bitMode, x, y, + ASig); + + InterpolateColours(Colours5554[0][0].Reps[1], + Colours5554[0][1].Reps[1], + Colours5554[1][0].Reps[1], + Colours5554[1][1].Reps[1], + Do2bitMode, x, y, + BSig); + + GetModulationValue(x,y, Do2bitMode, (const int (*)[16])ModulationVals, (const int (*)[16])ModulationModes, + &Mod, &DoPT); + + /* + // compute the modulated colour + */ + for(i = 0; i < 4; i++) + { + Result[i] = ASig[i] * 8 + Mod * (BSig[i] - ASig[i]); + Result[i] >>= 3; + } + if(DoPT) + { + Result[3] = 0; + } + + /* + // Store the result in the output image + */ + uPosition = (x+y*XDim)<<2; + pResultImage[uPosition+0] = (unsigned char)Result[0]; + pResultImage[uPosition+1] = (unsigned char)Result[1]; + pResultImage[uPosition+2] = (unsigned char)Result[2]; + pResultImage[uPosition+3] = (unsigned char)Result[3]; + + }/*end for x*/ + }/*end for y*/ + +} + +static void * stbi__pvr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi_uc *pvr_data = NULL; + stbi_uc *pvr_res_data = NULL; + PVR_Texture_Header header={0}; + int iscompressed = 0; + int bitmode = 0; + unsigned int levelSize = 0; + + stbi__getn( s, (stbi_uc*)(&header), sizeof(PVR_Texture_Header) ); + + // Check the header size + if ( header.dwHeaderSize != sizeof(PVR_Texture_Header) ) { + return NULL; + } + + // Check the magic identifier + if ( header.dwPVR != PVRTEX_IDENTIFIER ) { + return NULL; + } + + *x = s->img_x = header.dwWidth; + *y = s->img_y = header.dwHeight; + + /* Get if the texture is compressed and the texture mode ( 2bpp or 4bpp ) */ + switch ( header.dwpfFlags & PVRTEX_PIXELTYPE ) + { + case OGL_RGBA_4444: + s->img_n = 2; + break; + case OGL_RGBA_5551: + s->img_n = 2; + break; + case OGL_RGBA_8888: + s->img_n = 4; + break; + case OGL_RGB_565: + s->img_n = 2; + break; + case OGL_RGB_888: + s->img_n = 3; + break; + case OGL_I_8: + s->img_n = 1; + break; + case OGL_AI_88: + s->img_n = 2; + break; + case OGL_PVRTC2: + bitmode = 1; + s->img_n = 4; + iscompressed = 1; + break; + case OGL_PVRTC4: + s->img_n = 4; + iscompressed = 1; + break; + case OGL_RGB_555: + default: + return NULL; + } + + *comp = s->img_n; + + // Load only the first mip map level + levelSize = (s->img_x * s->img_y * header.dwBitCount + 7) / 8; + + // get the raw data + pvr_data = (stbi_uc *)malloc( levelSize ); + stbi__getn( s, pvr_data, levelSize ); + + // if compressed decompress as RGBA + if ( iscompressed ) { + pvr_res_data = (stbi_uc *)malloc( s->img_x * s->img_y * 4 ); + Decompress( (AMTC_BLOCK_STRUCT*)pvr_data, bitmode, s->img_x, s->img_y, 1, (unsigned char*)pvr_res_data ); + free( pvr_data ); + } else { + // otherwise use the raw data + pvr_res_data = pvr_data; + } + + if( (req_comp <= 4) && (req_comp >= 1) ) { + // user has some requirements, meet them + if( req_comp != s->img_n ) { + pvr_res_data = stbi__convert_format( pvr_res_data, s->img_n, req_comp, s->img_x, s->img_y ); + *comp = req_comp; + } + } + + return pvr_res_data; +} + +#ifndef STBI_NO_STDIO +void *stbi__pvr_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_file(&s,f); + return stbi__pvr_load(&s,x,y,comp,req_comp); +} + +void *stbi__pvr_load_from_path (char const*filename, int *x, int *y, int *comp, int req_comp) +{ + void *data; + FILE *f = fopen(filename, "rb"); + if (!f) return NULL; + data = stbi__pvr_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return data; +} +#endif + +void *stbi__pvr_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer, len); + return stbi__pvr_load(&s,x,y,comp,req_comp); +} + +void *stbi__pvr_load_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__pvr_load(&s,x,y,comp,req_comp); +} diff --git a/lib/soil/stb_image_aug.c b/lib/soil/stb_image_aug.c deleted file mode 100644 index cd598d6af..000000000 --- a/lib/soil/stb_image_aug.c +++ /dev/null @@ -1,3698 +0,0 @@ -/* stbi-1.16 - public domain JPEG/PNG reader - http://nothings.org/stb_image.c - when you control the images you're loading - - QUICK NOTES: - Primarily of interest to game developers and other people who can - avoid problematic images and only need the trivial interface - - JPEG baseline (no JPEG progressive, no oddball channel decimations) - PNG non-interlaced - BMP non-1bpp, non-RLE - TGA (not sure what subset, if a subset) - PSD (composited view only, no extra channels) - HDR (radiance rgbE format) - writes BMP,TGA (define STBI_NO_WRITE to remove code) - decoded from memory or through stdio FILE (define STBI_NO_STDIO to remove code) - supports installable dequantizing-IDCT, YCbCr-to-RGB conversion (define STBI_SIMD) - - TODO: - stbi_info_* - - history: - 1.16 major bugfix - convert_format converted one too many pixels - 1.15 initialize some fields for thread safety - 1.14 fix threadsafe conversion bug; header-file-only version (#define STBI_HEADER_FILE_ONLY before including) - 1.13 threadsafe - 1.12 const qualifiers in the API - 1.11 Support installable IDCT, colorspace conversion routines - 1.10 Fixes for 64-bit (don't use "unsigned long") - optimized upsampling by Fabian "ryg" Giesen - 1.09 Fix format-conversion for PSD code (bad global variables!) - 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz - 1.07 attempt to fix C++ warning/errors again - 1.06 attempt to fix C++ warning/errors again - 1.05 fix TGA loading to return correct *comp and use good luminance calc - 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free - 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR - 1.02 support for (subset of) HDR files, float interface for preferred access to them - 1.01 fix bug: possible bug in handling right-side up bmps... not sure - fix bug: the stbi_bmp_load() and stbi_tga_load() functions didn't work at all - 1.00 interface to zlib that skips zlib header - 0.99 correct handling of alpha in palette - 0.98 TGA loader by lonesock; dynamically add loaders (untested) - 0.97 jpeg errors on too large a file; also catch another malloc failure - 0.96 fix detection of invalid v value - particleman@mollyrocket forum - 0.95 during header scan, seek to markers in case of padding - 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same - 0.93 handle jpegtran output; verbose errors - 0.92 read 4,8,16,24,32-bit BMP files of several formats - 0.91 output 24-bit Windows 3.0 BMP files - 0.90 fix a few more warnings; bump version number to approach 1.0 - 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd - 0.60 fix compiling as c++ - 0.59 fix warnings: merge Dave Moore's -Wall fixes - 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian - 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less - than 16 available - 0.56 fix bug: zlib uncompressed mode len vs. nlen - 0.55 fix bug: restart_interval not initialized to 0 - 0.54 allow NULL for 'int *comp' - 0.53 fix bug in png 3->4; speedup png decoding - 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments - 0.51 obey req_comp requests, 1-component jpegs return as 1-component, - on 'test' only check type, not whether we support this variant -*/ - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable: 4018 4100 4244) -#else -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wsign-compare" -#pragma GCC diagnostic ignored "-Wunused-parameter" -#pragma GCC diagnostic ignored "-Wunused-but-set-variable" -#endif - -#include "stb_image_aug.h" - -#ifndef STBI_NO_HDR -#include // ldexp -#include // strcmp -#endif - -#ifndef STBI_NO_STDIO -#include -#endif -#include -#include -#include -#include - -#ifndef _MSC_VER - #ifdef __cplusplus - #define __forceinline inline - #else - #define __forceinline - #endif -#endif - - -// implementation: -typedef unsigned char uint8; -typedef unsigned short uint16; -typedef signed short int16; -typedef unsigned int uint32; -typedef signed int int32; -typedef unsigned int uint; - -// should produce compiler error if size is wrong -typedef unsigned char validate_uint32[sizeof(uint32)==4]; - -#if defined(STBI_NO_STDIO) && !defined(STBI_NO_WRITE) -#define STBI_NO_WRITE -#endif - -#ifndef STBI_NO_DDS -#include "stbi_DDS_aug.h" -#endif - -// I (JLD) want full messages for SOIL -#define STBI_FAILURE_USERMSG 1 - -////////////////////////////////////////////////////////////////////////////// -// -// Generic API that works on all image types -// - -// this is not threadsafe -static char *failure_reason; - -char *stbi_failure_reason(void) -{ - return failure_reason; -} - -static int e(char *str) -{ - failure_reason = str; - return 0; -} - -#ifdef STBI_NO_FAILURE_STRINGS - #define e(x,y) 0 -#elif defined(STBI_FAILURE_USERMSG) - #define e(x,y) e(y) -#else - #define e(x,y) e(x) -#endif - -#define epf(x,y) ((float *) (e(x,y)?NULL:NULL)) -#define epuc(x,y) ((unsigned char *) (e(x,y)?NULL:NULL)) - -void stbi_image_free(void *retval_from_stbi_load) -{ - free(retval_from_stbi_load); -} - -#define MAX_LOADERS 32 -stbi_loader *loaders[MAX_LOADERS]; -static int max_loaders = 0; - -int stbi_register_loader(stbi_loader *loader) -{ - int i; - for (i=0; i < MAX_LOADERS; ++i) { - // already present? - if (loaders[i] == loader) - return 1; - // end of the list? - if (loaders[i] == NULL) { - loaders[i] = loader; - max_loaders = i+1; - return 1; - } - } - // no room for it - return 0; -} - -#ifndef STBI_NO_HDR -static float *ldr_to_hdr(stbi_uc *data, int x, int y, int comp); -static stbi_uc *hdr_to_ldr(float *data, int x, int y, int comp); -#endif - -#ifndef STBI_NO_STDIO -unsigned char *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) -{ - FILE *f = fopen(filename, "rb"); - unsigned char *result; - if (!f) return epuc("can't fopen", "Unable to open file"); - result = stbi_load_from_file(f,x,y,comp,req_comp); - fclose(f); - return result; -} - -unsigned char *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) -{ - int i; - if (stbi_jpeg_test_file(f)) - return stbi_jpeg_load_from_file(f,x,y,comp,req_comp); - if (stbi_png_test_file(f)) - return stbi_png_load_from_file(f,x,y,comp,req_comp); - if (stbi_bmp_test_file(f)) - return stbi_bmp_load_from_file(f,x,y,comp,req_comp); - if (stbi_psd_test_file(f)) - return stbi_psd_load_from_file(f,x,y,comp,req_comp); - #ifndef STBI_NO_DDS - if (stbi_dds_test_file(f)) - return stbi_dds_load_from_file(f,x,y,comp,req_comp); - #endif - #ifndef STBI_NO_HDR - if (stbi_hdr_test_file(f)) { - float *hdr = stbi_hdr_load_from_file(f, x,y,comp,req_comp); - return hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); - } - #endif - for (i=0; i < max_loaders; ++i) - if (loaders[i]->test_file(f)) - return loaders[i]->load_from_file(f,x,y,comp,req_comp); - // test tga last because it's a crappy test! - if (stbi_tga_test_file(f)) - return stbi_tga_load_from_file(f,x,y,comp,req_comp); - return epuc("unknown image type", "Image not of any known type, or corrupt"); -} -#endif - -unsigned char *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) -{ - int i; - if (stbi_jpeg_test_memory(buffer,len)) - return stbi_jpeg_load_from_memory(buffer,len,x,y,comp,req_comp); - if (stbi_png_test_memory(buffer,len)) - return stbi_png_load_from_memory(buffer,len,x,y,comp,req_comp); - if (stbi_bmp_test_memory(buffer,len)) - return stbi_bmp_load_from_memory(buffer,len,x,y,comp,req_comp); - if (stbi_psd_test_memory(buffer,len)) - return stbi_psd_load_from_memory(buffer,len,x,y,comp,req_comp); - #ifndef STBI_NO_DDS - if (stbi_dds_test_memory(buffer,len)) - return stbi_dds_load_from_memory(buffer,len,x,y,comp,req_comp); - #endif - #ifndef STBI_NO_HDR - if (stbi_hdr_test_memory(buffer, len)) { - float *hdr = stbi_hdr_load_from_memory(buffer, len,x,y,comp,req_comp); - return hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); - } - #endif - for (i=0; i < max_loaders; ++i) - if (loaders[i]->test_memory(buffer,len)) - return loaders[i]->load_from_memory(buffer,len,x,y,comp,req_comp); - // test tga last because it's a crappy test! - if (stbi_tga_test_memory(buffer,len)) - return stbi_tga_load_from_memory(buffer,len,x,y,comp,req_comp); - return epuc("unknown image type", "Image not of any known type, or corrupt"); -} - -#ifndef STBI_NO_HDR - -#ifndef STBI_NO_STDIO -float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) -{ - FILE *f = fopen(filename, "rb"); - float *result; - if (!f) return epf("can't fopen", "Unable to open file"); - result = stbi_loadf_from_file(f,x,y,comp,req_comp); - fclose(f); - return result; -} - -float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) -{ - unsigned char *data; - #ifndef STBI_NO_HDR - if (stbi_hdr_test_file(f)) - return stbi_hdr_load_from_file(f,x,y,comp,req_comp); - #endif - data = stbi_load_from_file(f, x, y, comp, req_comp); - if (data) - return ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); - return epf("unknown image type", "Image not of any known type, or corrupt"); -} -#endif - -float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) -{ - stbi_uc *data; - #ifndef STBI_NO_HDR - if (stbi_hdr_test_memory(buffer, len)) - return stbi_hdr_load_from_memory(buffer, len,x,y,comp,req_comp); - #endif - data = stbi_load_from_memory(buffer, len, x, y, comp, req_comp); - if (data) - return ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); - return epf("unknown image type", "Image not of any known type, or corrupt"); -} -#endif - -// these is-hdr-or-not is defined independent of whether STBI_NO_HDR is -// defined, for API simplicity; if STBI_NO_HDR is defined, it always -// reports false! - -int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) -{ - #ifndef STBI_NO_HDR - return stbi_hdr_test_memory(buffer, len); - #else - return 0; - #endif -} - -#ifndef STBI_NO_STDIO -extern int stbi_is_hdr (char const *filename) -{ - FILE *f = fopen(filename, "rb"); - int result=0; - if (f) { - result = stbi_is_hdr_from_file(f); - fclose(f); - } - return result; -} - -extern int stbi_is_hdr_from_file(FILE *f) -{ - #ifndef STBI_NO_HDR - return stbi_hdr_test_file(f); - #else - return 0; - #endif -} - -#endif - -// @TODO: get image dimensions & components without fully decoding -#ifndef STBI_NO_STDIO -extern int stbi_info (char const *filename, int *x, int *y, int *comp); -extern int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); -#endif -extern int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); - -#ifndef STBI_NO_HDR -static float h2l_gamma_i=1.0f/2.2f, h2l_scale_i=1.0f; -static float l2h_gamma=2.2f, l2h_scale=1.0f; - -void stbi_hdr_to_ldr_gamma(float gamma) { h2l_gamma_i = 1/gamma; } -void stbi_hdr_to_ldr_scale(float scale) { h2l_scale_i = 1/scale; } - -void stbi_ldr_to_hdr_gamma(float gamma) { l2h_gamma = gamma; } -void stbi_ldr_to_hdr_scale(float scale) { l2h_scale = scale; } -#endif - - -////////////////////////////////////////////////////////////////////////////// -// -// Common code used by all image loaders -// - -enum -{ - SCAN_load=0, - SCAN_type, - SCAN_header, -}; - -typedef struct -{ - uint32 img_x, img_y; - int img_n, img_out_n; - - #ifndef STBI_NO_STDIO - FILE *img_file; - #endif - uint8 *img_buffer, *img_buffer_end; -} stbi; - -#ifndef STBI_NO_STDIO -static void start_file(stbi *s, FILE *f) -{ - s->img_file = f; -} -#endif - -static void start_mem(stbi *s, uint8 const *buffer, int len) -{ -#ifndef STBI_NO_STDIO - s->img_file = NULL; -#endif - s->img_buffer = (uint8 *) buffer; - s->img_buffer_end = (uint8 *) buffer+len; -} - -__forceinline static int get8(stbi *s) -{ -#ifndef STBI_NO_STDIO - if (s->img_file) { - int c = fgetc(s->img_file); - return c == EOF ? 0 : c; - } -#endif - if (s->img_buffer < s->img_buffer_end) - return *s->img_buffer++; - return 0; -} - -__forceinline static int at_eof(stbi *s) -{ -#ifndef STBI_NO_STDIO - if (s->img_file) - return feof(s->img_file); -#endif - return s->img_buffer >= s->img_buffer_end; -} - -__forceinline static uint8 get8u(stbi *s) -{ - return (uint8) get8(s); -} - -static void skip(stbi *s, int n) -{ -#ifndef STBI_NO_STDIO - if (s->img_file) - fseek(s->img_file, n, SEEK_CUR); - else -#endif - s->img_buffer += n; -} - -static int get16(stbi *s) -{ - int z = get8(s); - return (z << 8) + get8(s); -} - -static uint32 get32(stbi *s) -{ - uint32 z = get16(s); - return (z << 16) + get16(s); -} - -static int get16le(stbi *s) -{ - int z = get8(s); - return z + (get8(s) << 8); -} - -static uint32 get32le(stbi *s) -{ - uint32 z = get16le(s); - return z + (get16le(s) << 16); -} - -static void getn(stbi *s, stbi_uc *buffer, int n) -{ -#ifndef STBI_NO_STDIO - if (s->img_file) { - fread(buffer, 1, n, s->img_file); - return; - } -#endif - memcpy(buffer, s->img_buffer, n); - s->img_buffer += n; -} - -////////////////////////////////////////////////////////////////////////////// -// -// generic converter from built-in img_n to req_comp -// individual types do this automatically as much as possible (e.g. jpeg -// does all cases internally since it needs to colorspace convert anyway, -// and it never has alpha, so very few cases ). png can automatically -// interleave an alpha=255 channel, but falls back to this for other cases -// -// assume data buffer is malloced, so malloc a new one and free that one -// only failure mode is malloc failing - -static uint8 compute_y(int r, int g, int b) -{ - return (uint8) (((r*77) + (g*150) + (29*b)) >> 8); -} - -static unsigned char *convert_format(unsigned char *data, int img_n, int req_comp, uint x, uint y) -{ - int i,j; - unsigned char *good; - - if (req_comp == img_n) return data; - assert(req_comp >= 1 && req_comp <= 4); - - good = (unsigned char *) malloc(req_comp * x * y); - if (good == NULL) { - free(data); - return epuc("outofmem", "Out of memory"); - } - - for (j=0; j < (int) y; ++j) { - unsigned char *src = data + j * x * img_n ; - unsigned char *dest = good + j * x * req_comp; - - #define COMBO(a,b) ((a)*8+(b)) - #define CASE(a,b) case COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) - // convert source image with img_n components to one with req_comp components; - // avoid switch per pixel, so use switch per scanline and massive macros - switch(COMBO(img_n, req_comp)) { - CASE(1,2) dest[0]=src[0], dest[1]=255; break; - CASE(1,3) dest[0]=dest[1]=dest[2]=src[0]; break; - CASE(1,4) dest[0]=dest[1]=dest[2]=src[0], dest[3]=255; break; - CASE(2,1) dest[0]=src[0]; break; - CASE(2,3) dest[0]=dest[1]=dest[2]=src[0]; break; - CASE(2,4) dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; break; - CASE(3,4) dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=255; break; - CASE(3,1) dest[0]=compute_y(src[0],src[1],src[2]); break; - CASE(3,2) dest[0]=compute_y(src[0],src[1],src[2]), dest[1] = 255; break; - CASE(4,1) dest[0]=compute_y(src[0],src[1],src[2]); break; - CASE(4,2) dest[0]=compute_y(src[0],src[1],src[2]), dest[1] = src[3]; break; - CASE(4,3) dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; break; - default: assert(0); - } - #undef CASE - } - - free(data); - return good; -} - -#ifndef STBI_NO_HDR -static float *ldr_to_hdr(stbi_uc *data, int x, int y, int comp) -{ - int i,k,n; - float *output = (float *) malloc(x * y * comp * sizeof(float)); - if (output == NULL) { free(data); return epf("outofmem", "Out of memory"); } - // compute number of non-alpha components - if (comp & 1) n = comp; else n = comp-1; - for (i=0; i < x*y; ++i) { - for (k=0; k < n; ++k) { - output[i*comp + k] = (float) pow(data[i*comp+k]/255.0f, l2h_gamma) * l2h_scale; - } - if (k < comp) output[i*comp + k] = data[i*comp+k]/255.0f; - } - free(data); - return output; -} - -#define float2int(x) ((int) (x)) -static stbi_uc *hdr_to_ldr(float *data, int x, int y, int comp) -{ - int i,k,n; - stbi_uc *output = (stbi_uc *) malloc(x * y * comp); - if (output == NULL) { free(data); return epuc("outofmem", "Out of memory"); } - // compute number of non-alpha components - if (comp & 1) n = comp; else n = comp-1; - for (i=0; i < x*y; ++i) { - for (k=0; k < n; ++k) { - float z = (float) pow(data[i*comp+k]*h2l_scale_i, h2l_gamma_i) * 255 + 0.5f; - if (z < 0) z = 0; - if (z > 255) z = 255; - output[i*comp + k] = float2int(z); - } - if (k < comp) { - float z = data[i*comp+k] * 255 + 0.5f; - if (z < 0) z = 0; - if (z > 255) z = 255; - output[i*comp + k] = float2int(z); - } - } - free(data); - return output; -} -#endif - -////////////////////////////////////////////////////////////////////////////// -// -// "baseline" JPEG/JFIF decoder (not actually fully baseline implementation) -// -// simple implementation -// - channel subsampling of at most 2 in each dimension -// - doesn't support delayed output of y-dimension -// - simple interface (only one output format: 8-bit interleaved RGB) -// - doesn't try to recover corrupt jpegs -// - doesn't allow partial loading, loading multiple at once -// - still fast on x86 (copying globals into locals doesn't help x86) -// - allocates lots of intermediate memory (full size of all components) -// - non-interleaved case requires this anyway -// - allows good upsampling (see next) -// high-quality -// - upsampled channels are bilinearly interpolated, even across blocks -// - quality integer IDCT derived from IJG's 'slow' -// performance -// - fast huffman; reasonable integer IDCT -// - uses a lot of intermediate memory, could cache poorly -// - load http://nothings.org/remote/anemones.jpg 3 times on 2.8Ghz P4 -// stb_jpeg: 1.34 seconds (MSVC6, default release build) -// stb_jpeg: 1.06 seconds (MSVC6, processor = Pentium Pro) -// IJL11.dll: 1.08 seconds (compiled by intel) -// IJG 1998: 0.98 seconds (MSVC6, makefile provided by IJG) -// IJG 1998: 0.95 seconds (MSVC6, makefile + proc=PPro) - -// huffman decoding acceleration -#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache - -typedef struct -{ - uint8 fast[1 << FAST_BITS]; - // weirdly, repacking this into AoS is a 10% speed loss, instead of a win - uint16 code[256]; - uint8 values[256]; - uint8 size[257]; - unsigned int maxcode[18]; - int delta[17]; // old 'firstsymbol' - old 'firstcode' -} huffman; - -typedef struct -{ - #if STBI_SIMD - unsigned short dequant2[4][64]; - #endif - stbi s; - huffman huff_dc[4]; - huffman huff_ac[4]; - uint8 dequant[4][64]; - -// sizes for components, interleaved MCUs - int img_h_max, img_v_max; - int img_mcu_x, img_mcu_y; - int img_mcu_w, img_mcu_h; - -// definition of jpeg image component - struct - { - int id; - int h,v; - int tq; - int hd,ha; - int dc_pred; - - int x,y,w2,h2; - uint8 *data; - void *raw_data; - uint8 *linebuf; - } img_comp[4]; - - uint32 code_buffer; // jpeg entropy-coded buffer - int code_bits; // number of valid bits - unsigned char marker; // marker seen while filling entropy buffer - int nomore; // flag if we saw a marker so must stop - - int scan_n, order[4]; - int restart_interval, todo; -} jpeg; - -static int build_huffman(huffman *h, int *count) -{ - int i,j,k=0,code; - // build size list for each symbol (from JPEG spec) - for (i=0; i < 16; ++i) - for (j=0; j < count[i]; ++j) - h->size[k++] = (uint8) (i+1); - h->size[k] = 0; - - // compute actual symbols (from jpeg spec) - code = 0; - k = 0; - for(j=1; j <= 16; ++j) { - // compute delta to add to code to compute symbol id - h->delta[j] = k - code; - if (h->size[k] == j) { - while (h->size[k] == j) - h->code[k++] = (uint16) (code++); - if (code-1 >= (1 << j)) return e("bad code lengths","Corrupt JPEG"); - } - // compute largest code + 1 for this size, preshifted as needed later - h->maxcode[j] = code << (16-j); - code <<= 1; - } - h->maxcode[j] = 0xffffffff; - - // build non-spec acceleration table; 255 is flag for not-accelerated - memset(h->fast, 255, 1 << FAST_BITS); - for (i=0; i < k; ++i) { - int s = h->size[i]; - if (s <= FAST_BITS) { - int c = h->code[i] << (FAST_BITS-s); - int m = 1 << (FAST_BITS-s); - for (j=0; j < m; ++j) { - h->fast[c+j] = (uint8) i; - } - } - } - return 1; -} - -static void grow_buffer_unsafe(jpeg *j) -{ - do { - int b = j->nomore ? 0 : get8(&j->s); - if (b == 0xff) { - int c = get8(&j->s); - if (c != 0) { - j->marker = (unsigned char) c; - j->nomore = 1; - return; - } - } - j->code_buffer = (j->code_buffer << 8) | b; - j->code_bits += 8; - } while (j->code_bits <= 24); -} - -// (1 << n) - 1 -static uint32 bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; - -// decode a jpeg huffman value from the bitstream -__forceinline static int decode(jpeg *j, huffman *h) -{ - unsigned int temp; - int c,k; - - if (j->code_bits < 16) grow_buffer_unsafe(j); - - // look at the top FAST_BITS and determine what symbol ID it is, - // if the code is <= FAST_BITS - c = (j->code_buffer >> (j->code_bits - FAST_BITS)) & ((1 << FAST_BITS)-1); - k = h->fast[c]; - if (k < 255) { - if (h->size[k] > j->code_bits) - return -1; - j->code_bits -= h->size[k]; - return h->values[k]; - } - - // naive test is to shift the code_buffer down so k bits are - // valid, then test against maxcode. To speed this up, we've - // preshifted maxcode left so that it has (16-k) 0s at the - // end; in other words, regardless of the number of bits, it - // wants to be compared against something shifted to have 16; - // that way we don't need to shift inside the loop. - if (j->code_bits < 16) - temp = (j->code_buffer << (16 - j->code_bits)) & 0xffff; - else - temp = (j->code_buffer >> (j->code_bits - 16)) & 0xffff; - for (k=FAST_BITS+1 ; ; ++k) - if (temp < h->maxcode[k]) - break; - if (k == 17) { - // error! code not found - j->code_bits -= 16; - return -1; - } - - if (k > j->code_bits) - return -1; - - // convert the huffman code to the symbol id - c = ((j->code_buffer >> (j->code_bits - k)) & bmask[k]) + h->delta[k]; - assert((((j->code_buffer) >> (j->code_bits - h->size[c])) & bmask[h->size[c]]) == h->code[c]); - - // convert the id to a symbol - j->code_bits -= k; - return h->values[c]; -} - -// combined JPEG 'receive' and JPEG 'extend', since baseline -// always extends everything it receives. -__forceinline static int extend_receive(jpeg *j, int n) -{ - unsigned int m = 1 << (n-1); - unsigned int k; - if (j->code_bits < n) grow_buffer_unsafe(j); - k = (j->code_buffer >> (j->code_bits - n)) & bmask[n]; - j->code_bits -= n; - // the following test is probably a random branch that won't - // predict well. I tried to table accelerate it but failed. - // maybe it's compiling as a conditional move? - if (k < m) - return (-1 << n) + k + 1; - else - return k; -} - -// given a value that's at position X in the zigzag stream, -// where does it appear in the 8x8 matrix coded as row-major? -static uint8 dezigzag[64+15] = -{ - 0, 1, 8, 16, 9, 2, 3, 10, - 17, 24, 32, 25, 18, 11, 4, 5, - 12, 19, 26, 33, 40, 48, 41, 34, - 27, 20, 13, 6, 7, 14, 21, 28, - 35, 42, 49, 56, 57, 50, 43, 36, - 29, 22, 15, 23, 30, 37, 44, 51, - 58, 59, 52, 45, 38, 31, 39, 46, - 53, 60, 61, 54, 47, 55, 62, 63, - // let corrupt input sample past end - 63, 63, 63, 63, 63, 63, 63, 63, - 63, 63, 63, 63, 63, 63, 63 -}; - -// decode one 64-entry block-- -static int decode_block(jpeg *j, short data[64], huffman *hdc, huffman *hac, int b) -{ - int diff,dc,k; - int t = decode(j, hdc); - if (t < 0) return e("bad huffman code","Corrupt JPEG"); - - // 0 all the ac values now so we can do it 32-bits at a time - memset(data,0,64*sizeof(data[0])); - - diff = t ? extend_receive(j, t) : 0; - dc = j->img_comp[b].dc_pred + diff; - j->img_comp[b].dc_pred = dc; - data[0] = (short) dc; - - // decode AC components, see JPEG spec - k = 1; - do { - int r,s; - int rs = decode(j, hac); - if (rs < 0) return e("bad huffman code","Corrupt JPEG"); - s = rs & 15; - r = rs >> 4; - if (s == 0) { - if (rs != 0xf0) break; // end block - k += 16; - } else { - k += r; - // decode into unzigzag'd location - data[dezigzag[k++]] = (short) extend_receive(j,s); - } - } while (k < 64); - return 1; -} - -// take a -128..127 value and clamp it and convert to 0..255 -__forceinline static uint8 clamp(int x) -{ - x += 128; - // trick to use a single test to catch both cases - if ((unsigned int) x > 255) { - if (x < 0) return 0; - if (x > 255) return 255; - } - return (uint8) x; -} - -#define f2f(x) (int) (((x) * 4096 + 0.5)) -#define fsh(x) ((x) << 12) - -// derived from jidctint -- DCT_ISLOW -#define IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ - int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ - p2 = s2; \ - p3 = s6; \ - p1 = (p2+p3) * f2f(0.5411961f); \ - t2 = p1 + p3*f2f(-1.847759065f); \ - t3 = p1 + p2*f2f( 0.765366865f); \ - p2 = s0; \ - p3 = s4; \ - t0 = fsh(p2+p3); \ - t1 = fsh(p2-p3); \ - x0 = t0+t3; \ - x3 = t0-t3; \ - x1 = t1+t2; \ - x2 = t1-t2; \ - t0 = s7; \ - t1 = s5; \ - t2 = s3; \ - t3 = s1; \ - p3 = t0+t2; \ - p4 = t1+t3; \ - p1 = t0+t3; \ - p2 = t1+t2; \ - p5 = (p3+p4)*f2f( 1.175875602f); \ - t0 = t0*f2f( 0.298631336f); \ - t1 = t1*f2f( 2.053119869f); \ - t2 = t2*f2f( 3.072711026f); \ - t3 = t3*f2f( 1.501321110f); \ - p1 = p5 + p1*f2f(-0.899976223f); \ - p2 = p5 + p2*f2f(-2.562915447f); \ - p3 = p3*f2f(-1.961570560f); \ - p4 = p4*f2f(-0.390180644f); \ - t3 += p1+p4; \ - t2 += p2+p3; \ - t1 += p2+p4; \ - t0 += p1+p3; - -#if !STBI_SIMD -// .344 seconds on 3*anemones.jpg -static void idct_block(uint8 *out, int out_stride, short data[64], uint8 *dequantize) -{ - int i,val[64],*v=val; - uint8 *o,*dq = dequantize; - short *d = data; - - // columns - for (i=0; i < 8; ++i,++d,++dq, ++v) { - // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing - if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 - && d[40]==0 && d[48]==0 && d[56]==0) { - // no shortcut 0 seconds - // (1|2|3|4|5|6|7)==0 0 seconds - // all separate -0.047 seconds - // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds - int dcterm = d[0] * dq[0] << 2; - v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; - } else { - IDCT_1D(d[ 0]*dq[ 0],d[ 8]*dq[ 8],d[16]*dq[16],d[24]*dq[24], - d[32]*dq[32],d[40]*dq[40],d[48]*dq[48],d[56]*dq[56]) - // constants scaled things up by 1<<12; let's bring them back - // down, but keep 2 extra bits of precision - x0 += 512; x1 += 512; x2 += 512; x3 += 512; - v[ 0] = (x0+t3) >> 10; - v[56] = (x0-t3) >> 10; - v[ 8] = (x1+t2) >> 10; - v[48] = (x1-t2) >> 10; - v[16] = (x2+t1) >> 10; - v[40] = (x2-t1) >> 10; - v[24] = (x3+t0) >> 10; - v[32] = (x3-t0) >> 10; - } - } - - for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { - // no fast case since the first 1D IDCT spread components out - IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) - // constants scaled things up by 1<<12, plus we had 1<<2 from first - // loop, plus horizontal and vertical each scale by sqrt(8) so together - // we've got an extra 1<<3, so 1<<17 total we need to remove. - x0 += 65536; x1 += 65536; x2 += 65536; x3 += 65536; - o[0] = clamp((x0+t3) >> 17); - o[7] = clamp((x0-t3) >> 17); - o[1] = clamp((x1+t2) >> 17); - o[6] = clamp((x1-t2) >> 17); - o[2] = clamp((x2+t1) >> 17); - o[5] = clamp((x2-t1) >> 17); - o[3] = clamp((x3+t0) >> 17); - o[4] = clamp((x3-t0) >> 17); - } -} -#else -static void idct_block(uint8 *out, int out_stride, short data[64], unsigned short *dequantize) -{ - int i,val[64],*v=val; - uint8 *o; - unsigned short *dq = dequantize; - short *d = data; - - // columns - for (i=0; i < 8; ++i,++d,++dq, ++v) { - // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing - if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 - && d[40]==0 && d[48]==0 && d[56]==0) { - // no shortcut 0 seconds - // (1|2|3|4|5|6|7)==0 0 seconds - // all separate -0.047 seconds - // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds - int dcterm = d[0] * dq[0] << 2; - v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; - } else { - IDCT_1D(d[ 0]*dq[ 0],d[ 8]*dq[ 8],d[16]*dq[16],d[24]*dq[24], - d[32]*dq[32],d[40]*dq[40],d[48]*dq[48],d[56]*dq[56]) - // constants scaled things up by 1<<12; let's bring them back - // down, but keep 2 extra bits of precision - x0 += 512; x1 += 512; x2 += 512; x3 += 512; - v[ 0] = (x0+t3) >> 10; - v[56] = (x0-t3) >> 10; - v[ 8] = (x1+t2) >> 10; - v[48] = (x1-t2) >> 10; - v[16] = (x2+t1) >> 10; - v[40] = (x2-t1) >> 10; - v[24] = (x3+t0) >> 10; - v[32] = (x3-t0) >> 10; - } - } - - for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { - // no fast case since the first 1D IDCT spread components out - IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) - // constants scaled things up by 1<<12, plus we had 1<<2 from first - // loop, plus horizontal and vertical each scale by sqrt(8) so together - // we've got an extra 1<<3, so 1<<17 total we need to remove. - x0 += 65536; x1 += 65536; x2 += 65536; x3 += 65536; - o[0] = clamp((x0+t3) >> 17); - o[7] = clamp((x0-t3) >> 17); - o[1] = clamp((x1+t2) >> 17); - o[6] = clamp((x1-t2) >> 17); - o[2] = clamp((x2+t1) >> 17); - o[5] = clamp((x2-t1) >> 17); - o[3] = clamp((x3+t0) >> 17); - o[4] = clamp((x3-t0) >> 17); - } -} -static stbi_idct_8x8 stbi_idct_installed = idct_block; - -extern void stbi_install_idct(stbi_idct_8x8 func) -{ - stbi_idct_installed = func; -} -#endif - -#define MARKER_none 0xff -// if there's a pending marker from the entropy stream, return that -// otherwise, fetch from the stream and get a marker. if there's no -// marker, return 0xff, which is never a valid marker value -static uint8 get_marker(jpeg *j) -{ - uint8 x; - if (j->marker != MARKER_none) { x = j->marker; j->marker = MARKER_none; return x; } - x = get8u(&j->s); - if (x != 0xff) return MARKER_none; - while (x == 0xff) - x = get8u(&j->s); - return x; -} - -// in each scan, we'll have scan_n components, and the order -// of the components is specified by order[] -#define RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) - -// after a restart interval, reset the entropy decoder and -// the dc prediction -static void reset(jpeg *j) -{ - j->code_bits = 0; - j->code_buffer = 0; - j->nomore = 0; - j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = 0; - j->marker = MARKER_none; - j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; - // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, - // since we don't even allow 1<<30 pixels -} - -static int parse_entropy_coded_data(jpeg *z) -{ - reset(z); - if (z->scan_n == 1) { - int i,j; - #if STBI_SIMD - __declspec(align(16)) - #endif - short data[64]; - int n = z->order[0]; - // non-interleaved data, we just need to process one block at a time, - // in trivial scanline order - // number of blocks to do just depends on how many actual "pixels" this - // component has, independent of interleaved MCU blocking and such - int w = (z->img_comp[n].x+7) >> 3; - int h = (z->img_comp[n].y+7) >> 3; - for (j=0; j < h; ++j) { - for (i=0; i < w; ++i) { - if (!decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+z->img_comp[n].ha, n)) return 0; - #if STBI_SIMD - stbi_idct_installed(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data, z->dequant2[z->img_comp[n].tq]); - #else - idct_block(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data, z->dequant[z->img_comp[n].tq]); - #endif - // every data block is an MCU, so countdown the restart interval - if (--z->todo <= 0) { - if (z->code_bits < 24) grow_buffer_unsafe(z); - // if it's NOT a restart, then just bail, so we get corrupt data - // rather than no data - if (!RESTART(z->marker)) return 1; - reset(z); - } - } - } - } else { // interleaved! - int i,j,k,x,y; - short data[64]; - for (j=0; j < z->img_mcu_y; ++j) { - for (i=0; i < z->img_mcu_x; ++i) { - // scan an interleaved mcu... process scan_n components in order - for (k=0; k < z->scan_n; ++k) { - int n = z->order[k]; - // scan out an mcu's worth of this component; that's just determined - // by the basic H and V specified for the component - for (y=0; y < z->img_comp[n].v; ++y) { - for (x=0; x < z->img_comp[n].h; ++x) { - int x2 = (i*z->img_comp[n].h + x)*8; - int y2 = (j*z->img_comp[n].v + y)*8; - if (!decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+z->img_comp[n].ha, n)) return 0; - #if STBI_SIMD - stbi_idct_installed(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data, z->dequant2[z->img_comp[n].tq]); - #else - idct_block(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data, z->dequant[z->img_comp[n].tq]); - #endif - } - } - } - // after all interleaved components, that's an interleaved MCU, - // so now count down the restart interval - if (--z->todo <= 0) { - if (z->code_bits < 24) grow_buffer_unsafe(z); - // if it's NOT a restart, then just bail, so we get corrupt data - // rather than no data - if (!RESTART(z->marker)) return 1; - reset(z); - } - } - } - } - return 1; -} - -static int process_marker(jpeg *z, int m) -{ - int L; - switch (m) { - case MARKER_none: // no marker found - return e("expected marker","Corrupt JPEG"); - - case 0xC2: // SOF - progressive - return e("progressive jpeg","JPEG format not supported (progressive)"); - - case 0xDD: // DRI - specify restart interval - if (get16(&z->s) != 4) return e("bad DRI len","Corrupt JPEG"); - z->restart_interval = get16(&z->s); - return 1; - - case 0xDB: // DQT - define quantization table - L = get16(&z->s)-2; - while (L > 0) { - int q = get8(&z->s); - int p = q >> 4; - int t = q & 15,i; - if (p != 0) return e("bad DQT type","Corrupt JPEG"); - if (t > 3) return e("bad DQT table","Corrupt JPEG"); - for (i=0; i < 64; ++i) - z->dequant[t][dezigzag[i]] = get8u(&z->s); - #if STBI_SIMD - for (i=0; i < 64; ++i) - z->dequant2[t][i] = dequant[t][i]; - #endif - L -= 65; - } - return L==0; - - case 0xC4: // DHT - define huffman table - L = get16(&z->s)-2; - while (L > 0) { - uint8 *v; - int sizes[16],i,m=0; - int q = get8(&z->s); - int tc = q >> 4; - int th = q & 15; - if (tc > 1 || th > 3) return e("bad DHT header","Corrupt JPEG"); - for (i=0; i < 16; ++i) { - sizes[i] = get8(&z->s); - m += sizes[i]; - } - L -= 17; - if (tc == 0) { - if (!build_huffman(z->huff_dc+th, sizes)) return 0; - v = z->huff_dc[th].values; - } else { - if (!build_huffman(z->huff_ac+th, sizes)) return 0; - v = z->huff_ac[th].values; - } - for (i=0; i < m; ++i) - v[i] = get8u(&z->s); - L -= m; - } - return L==0; - } - // check for comment block or APP blocks - if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { - skip(&z->s, get16(&z->s)-2); - return 1; - } - return 0; -} - -// after we see SOS -static int process_scan_header(jpeg *z) -{ - int i; - int Ls = get16(&z->s); - z->scan_n = get8(&z->s); - if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s.img_n) return e("bad SOS component count","Corrupt JPEG"); - if (Ls != 6+2*z->scan_n) return e("bad SOS len","Corrupt JPEG"); - for (i=0; i < z->scan_n; ++i) { - int id = get8(&z->s), which; - int q = get8(&z->s); - for (which = 0; which < z->s.img_n; ++which) - if (z->img_comp[which].id == id) - break; - if (which == z->s.img_n) return 0; - z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return e("bad DC huff","Corrupt JPEG"); - z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return e("bad AC huff","Corrupt JPEG"); - z->order[i] = which; - } - if (get8(&z->s) != 0) return e("bad SOS","Corrupt JPEG"); - get8(&z->s); // should be 63, but might be 0 - if (get8(&z->s) != 0) return e("bad SOS","Corrupt JPEG"); - - return 1; -} - -static int process_frame_header(jpeg *z, int scan) -{ - stbi *s = &z->s; - int Lf,p,i,q, h_max=1,v_max=1,c; - Lf = get16(s); if (Lf < 11) return e("bad SOF len","Corrupt JPEG"); // JPEG - p = get8(s); if (p != 8) return e("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline - s->img_y = get16(s); if (s->img_y == 0) return e("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG - s->img_x = get16(s); if (s->img_x == 0) return e("0 width","Corrupt JPEG"); // JPEG requires - c = get8(s); - if (c != 3 && c != 1) return e("bad component count","Corrupt JPEG"); // JFIF requires - s->img_n = c; - for (i=0; i < c; ++i) { - z->img_comp[i].data = NULL; - z->img_comp[i].linebuf = NULL; - } - - if (Lf != 8+3*s->img_n) return e("bad SOF len","Corrupt JPEG"); - - for (i=0; i < s->img_n; ++i) { - z->img_comp[i].id = get8(s); - if (z->img_comp[i].id != i+1) // JFIF requires - if (z->img_comp[i].id != i) // some version of jpegtran outputs non-JFIF-compliant files! - return e("bad component ID","Corrupt JPEG"); - q = get8(s); - z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return e("bad H","Corrupt JPEG"); - z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return e("bad V","Corrupt JPEG"); - z->img_comp[i].tq = get8(s); if (z->img_comp[i].tq > 3) return e("bad TQ","Corrupt JPEG"); - } - - if (scan != SCAN_load) return 1; - - if ((1 << 30) / s->img_x / s->img_n < s->img_y) return e("too large", "Image too large to decode"); - - for (i=0; i < s->img_n; ++i) { - if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; - if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; - } - - // compute interleaved mcu info - z->img_h_max = h_max; - z->img_v_max = v_max; - z->img_mcu_w = h_max * 8; - z->img_mcu_h = v_max * 8; - z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; - z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; - - for (i=0; i < s->img_n; ++i) { - // number of effective pixels (e.g. for non-interleaved MCU) - z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; - z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; - // to simplify generation, we'll allocate enough memory to decode - // the bogus oversized data from using interleaved MCUs and their - // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't - // discard the extra data until colorspace conversion - z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; - z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; - z->img_comp[i].raw_data = malloc(z->img_comp[i].w2 * z->img_comp[i].h2+15); - if (z->img_comp[i].raw_data == NULL) { - for(--i; i >= 0; --i) { - free(z->img_comp[i].raw_data); - z->img_comp[i].data = NULL; - } - return e("outofmem", "Out of memory"); - } - // align blocks for installable-idct using mmx/sse - z->img_comp[i].data = (uint8*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); - z->img_comp[i].linebuf = NULL; - } - - return 1; -} - -// use comparisons since in some cases we handle more than one case (e.g. SOF) -#define DNL(x) ((x) == 0xdc) -#define SOI(x) ((x) == 0xd8) -#define EOI(x) ((x) == 0xd9) -#define SOF(x) ((x) == 0xc0 || (x) == 0xc1) -#define SOS(x) ((x) == 0xda) - -static int decode_jpeg_header(jpeg *z, int scan) -{ - int m; - z->marker = MARKER_none; // initialize cached marker to empty - m = get_marker(z); - if (!SOI(m)) return e("no SOI","Corrupt JPEG"); - if (scan == SCAN_type) return 1; - m = get_marker(z); - while (!SOF(m)) { - if (!process_marker(z,m)) return 0; - m = get_marker(z); - while (m == MARKER_none) { - // some files have extra padding after their blocks, so ok, we'll scan - if (at_eof(&z->s)) return e("no SOF", "Corrupt JPEG"); - m = get_marker(z); - } - } - if (!process_frame_header(z, scan)) return 0; - return 1; -} - -static int decode_jpeg_image(jpeg *j) -{ - int m; - j->restart_interval = 0; - if (!decode_jpeg_header(j, SCAN_load)) return 0; - m = get_marker(j); - while (!EOI(m)) { - if (SOS(m)) { - if (!process_scan_header(j)) return 0; - if (!parse_entropy_coded_data(j)) return 0; - } else { - if (!process_marker(j, m)) return 0; - } - m = get_marker(j); - } - return 1; -} - -// static jfif-centered resampling (across block boundaries) - -typedef uint8 *(*resample_row_func)(uint8 *out, uint8 *in0, uint8 *in1, - int w, int hs); - -#define div4(x) ((uint8) ((x) >> 2)) - -static uint8 *resample_row_1(uint8 *out, uint8 *in_near, uint8 *in_far, int w, int hs) -{ - return in_near; -} - -static uint8* resample_row_v_2(uint8 *out, uint8 *in_near, uint8 *in_far, int w, int hs) -{ - // need to generate two samples vertically for every one in input - int i; - for (i=0; i < w; ++i) - out[i] = div4(3*in_near[i] + in_far[i] + 2); - return out; -} - -static uint8* resample_row_h_2(uint8 *out, uint8 *in_near, uint8 *in_far, int w, int hs) -{ - // need to generate two samples horizontally for every one in input - int i; - uint8 *input = in_near; - if (w == 1) { - // if only one sample, can't do any interpolation - out[0] = out[1] = input[0]; - return out; - } - - out[0] = input[0]; - out[1] = div4(input[0]*3 + input[1] + 2); - for (i=1; i < w-1; ++i) { - int n = 3*input[i]+2; - out[i*2+0] = div4(n+input[i-1]); - out[i*2+1] = div4(n+input[i+1]); - } - out[i*2+0] = div4(input[w-2]*3 + input[w-1] + 2); - out[i*2+1] = input[w-1]; - return out; -} - -#define div16(x) ((uint8) ((x) >> 4)) - -static uint8 *resample_row_hv_2(uint8 *out, uint8 *in_near, uint8 *in_far, int w, int hs) -{ - // need to generate 2x2 samples for every one in input - int i,t0,t1; - if (w == 1) { - out[0] = out[1] = div4(3*in_near[0] + in_far[0] + 2); - return out; - } - - t1 = 3*in_near[0] + in_far[0]; - out[0] = div4(t1+2); - for (i=1; i < w; ++i) { - t0 = t1; - t1 = 3*in_near[i]+in_far[i]; - out[i*2-1] = div16(3*t0 + t1 + 8); - out[i*2 ] = div16(3*t1 + t0 + 8); - } - out[w*2-1] = div4(t1+2); - return out; -} - -static uint8 *resample_row_generic(uint8 *out, uint8 *in_near, uint8 *in_far, int w, int hs) -{ - // resample with nearest-neighbor - int i,j; - for (i=0; i < w; ++i) - for (j=0; j < hs; ++j) - out[i*hs+j] = in_near[i]; - return out; -} - -#define float2fixed(x) ((int) ((x) * 65536 + 0.5)) - -// 0.38 seconds on 3*anemones.jpg (0.25 with processor = Pro) -// VC6 without processor=Pro is generating multiple LEAs per multiply! -static void YCbCr_to_RGB_row(uint8 *out, uint8 *y, uint8 *pcb, uint8 *pcr, int count, int step) -{ - int i; - for (i=0; i < count; ++i) { - int y_fixed = (y[i] << 16) + 32768; // rounding - int r,g,b; - int cr = pcr[i] - 128; - int cb = pcb[i] - 128; - r = y_fixed + cr*float2fixed(1.40200f); - g = y_fixed - cr*float2fixed(0.71414f) - cb*float2fixed(0.34414f); - b = y_fixed + cb*float2fixed(1.77200f); - r >>= 16; - g >>= 16; - b >>= 16; - if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } - if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } - if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } - out[0] = (uint8)r; - out[1] = (uint8)g; - out[2] = (uint8)b; - out[3] = 255; - out += step; - } -} - -#if STBI_SIMD -static stbi_YCbCr_to_RGB_run stbi_YCbCr_installed = YCbCr_to_RGB_row; - -void stbi_install_YCbCr_to_RGB(stbi_YCbCr_to_RGB_run func) -{ - stbi_YCbCr_installed = func; -} -#endif - - -// clean up the temporary component buffers -static void cleanup_jpeg(jpeg *j) -{ - int i; - for (i=0; i < j->s.img_n; ++i) { - if (j->img_comp[i].data) { - free(j->img_comp[i].raw_data); - j->img_comp[i].data = NULL; - } - if (j->img_comp[i].linebuf) { - free(j->img_comp[i].linebuf); - j->img_comp[i].linebuf = NULL; - } - } -} - -typedef struct -{ - resample_row_func resample; - uint8 *line0,*line1; - int hs,vs; // expansion factor in each axis - int w_lores; // horizontal pixels pre-expansion - int ystep; // how far through vertical expansion we are - int ypos; // which pre-expansion row we're on -} stbi_resample; - -static uint8 *load_jpeg_image(jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) -{ - int n, decode_n; - // validate req_comp - if (req_comp < 0 || req_comp > 4) return epuc("bad req_comp", "Internal error"); - z->s.img_n = 0; - - // load a jpeg image from whichever source - if (!decode_jpeg_image(z)) { cleanup_jpeg(z); return NULL; } - - // determine actual number of components to generate - n = req_comp ? req_comp : z->s.img_n; - - if (z->s.img_n == 3 && n < 3) - decode_n = 1; - else - decode_n = z->s.img_n; - - // resample and color-convert - { - int k; - uint i,j; - uint8 *output; - uint8 *coutput[4]; - - stbi_resample res_comp[4]; - - for (k=0; k < decode_n; ++k) { - stbi_resample *r = &res_comp[k]; - - // allocate line buffer big enough for upsampling off the edges - // with upsample factor of 4 - z->img_comp[k].linebuf = (uint8 *) malloc(z->s.img_x + 3); - if (!z->img_comp[k].linebuf) { cleanup_jpeg(z); return epuc("outofmem", "Out of memory"); } - - r->hs = z->img_h_max / z->img_comp[k].h; - r->vs = z->img_v_max / z->img_comp[k].v; - r->ystep = r->vs >> 1; - r->w_lores = (z->s.img_x + r->hs-1) / r->hs; - r->ypos = 0; - r->line0 = r->line1 = z->img_comp[k].data; - - if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; - else if (r->hs == 1 && r->vs == 2) r->resample = resample_row_v_2; - else if (r->hs == 2 && r->vs == 1) r->resample = resample_row_h_2; - else if (r->hs == 2 && r->vs == 2) r->resample = resample_row_hv_2; - else r->resample = resample_row_generic; - } - - // can't error after this so, this is safe - output = (uint8 *) malloc(n * z->s.img_x * z->s.img_y + 1); - if (!output) { cleanup_jpeg(z); return epuc("outofmem", "Out of memory"); } - - // now go ahead and resample - for (j=0; j < z->s.img_y; ++j) { - uint8 *out = output + n * z->s.img_x * j; - for (k=0; k < decode_n; ++k) { - stbi_resample *r = &res_comp[k]; - int y_bot = r->ystep >= (r->vs >> 1); - coutput[k] = r->resample(z->img_comp[k].linebuf, - y_bot ? r->line1 : r->line0, - y_bot ? r->line0 : r->line1, - r->w_lores, r->hs); - if (++r->ystep >= r->vs) { - r->ystep = 0; - r->line0 = r->line1; - if (++r->ypos < z->img_comp[k].y) - r->line1 += z->img_comp[k].w2; - } - } - if (n >= 3) { - uint8 *y = coutput[0]; - if (z->s.img_n == 3) { - #if STBI_SIMD - stbi_YCbCr_installed(out, y, coutput[1], coutput[2], z->s.img_x, n); - #else - YCbCr_to_RGB_row(out, y, coutput[1], coutput[2], z->s.img_x, n); - #endif - } else - for (i=0; i < z->s.img_x; ++i) { - out[0] = out[1] = out[2] = y[i]; - out[3] = 255; // not used if n==3 - out += n; - } - } else { - uint8 *y = coutput[0]; - if (n == 1) - for (i=0; i < z->s.img_x; ++i) out[i] = y[i]; - else - for (i=0; i < z->s.img_x; ++i) *out++ = y[i], *out++ = 255; - } - } - cleanup_jpeg(z); - *out_x = z->s.img_x; - *out_y = z->s.img_y; - if (comp) *comp = z->s.img_n; // report original components, not output - return output; - } -} - -#ifndef STBI_NO_STDIO -unsigned char *stbi_jpeg_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) -{ - jpeg j; - start_file(&j.s, f); - return load_jpeg_image(&j, x,y,comp,req_comp); -} - -unsigned char *stbi_jpeg_load(char const *filename, int *x, int *y, int *comp, int req_comp) -{ - unsigned char *data; - FILE *f = fopen(filename, "rb"); - if (!f) return NULL; - data = stbi_jpeg_load_from_file(f,x,y,comp,req_comp); - fclose(f); - return data; -} -#endif - -unsigned char *stbi_jpeg_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) -{ - jpeg j; - start_mem(&j.s, buffer,len); - return load_jpeg_image(&j, x,y,comp,req_comp); -} - -#ifndef STBI_NO_STDIO -int stbi_jpeg_test_file(FILE *f) -{ - int n,r; - jpeg j; - n = ftell(f); - start_file(&j.s, f); - r = decode_jpeg_header(&j, SCAN_type); - fseek(f,n,SEEK_SET); - return r; -} -#endif - -int stbi_jpeg_test_memory(stbi_uc const *buffer, int len) -{ - jpeg j; - start_mem(&j.s, buffer,len); - return decode_jpeg_header(&j, SCAN_type); -} - -// @TODO: -#ifndef STBI_NO_STDIO -extern int stbi_jpeg_info (char const *filename, int *x, int *y, int *comp); -extern int stbi_jpeg_info_from_file (FILE *f, int *x, int *y, int *comp); -#endif -extern int stbi_jpeg_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); - -// public domain zlib decode v0.2 Sean Barrett 2006-11-18 -// simple implementation -// - all input must be provided in an upfront buffer -// - all output is written to a single output buffer (can malloc/realloc) -// performance -// - fast huffman - -// fast-way is faster to check than jpeg huffman, but slow way is slower -#define ZFAST_BITS 9 // accelerate all cases in default tables -#define ZFAST_MASK ((1 << ZFAST_BITS) - 1) - -// zlib-style huffman encoding -// (jpegs packs from left, zlib from right, so can't share code) -typedef struct -{ - uint16 fast[1 << ZFAST_BITS]; - uint16 firstcode[16]; - int maxcode[17]; - uint16 firstsymbol[16]; - uint8 size[288]; - uint16 value[288]; -} zhuffman; - -__forceinline static int bitreverse16(int n) -{ - n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); - n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); - n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); - n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); - return n; -} - -__forceinline static int bit_reverse(int v, int bits) -{ - assert(bits <= 16); - // to bit reverse n bits, reverse 16 and shift - // e.g. 11 bits, bit reverse and shift away 5 - return bitreverse16(v) >> (16-bits); -} - -static int zbuild_huffman(zhuffman *z, uint8 *sizelist, int num) -{ - int i,k=0; - int code, next_code[16], sizes[17]; - - // DEFLATE spec for generating codes - memset(sizes, 0, sizeof(sizes)); - memset(z->fast, 255, sizeof(z->fast)); - for (i=0; i < num; ++i) - ++sizes[sizelist[i]]; - sizes[0] = 0; - for (i=1; i < 16; ++i) - assert(sizes[i] <= (1 << i)); - code = 0; - for (i=1; i < 16; ++i) { - next_code[i] = code; - z->firstcode[i] = (uint16) code; - z->firstsymbol[i] = (uint16) k; - code = (code + sizes[i]); - if (sizes[i]) - if (code-1 >= (1 << i)) return e("bad codelengths","Corrupt JPEG"); - z->maxcode[i] = code << (16-i); // preshift for inner loop - code <<= 1; - k += sizes[i]; - } - z->maxcode[16] = 0x10000; // sentinel - for (i=0; i < num; ++i) { - int s = sizelist[i]; - if (s) { - int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; - z->size[c] = (uint8)s; - z->value[c] = (uint16)i; - if (s <= ZFAST_BITS) { - int k = bit_reverse(next_code[s],s); - while (k < (1 << ZFAST_BITS)) { - z->fast[k] = (uint16) c; - k += (1 << s); - } - } - ++next_code[s]; - } - } - return 1; -} - -// zlib-from-memory implementation for PNG reading -// because PNG allows splitting the zlib stream arbitrarily, -// and it's annoying structurally to have PNG call ZLIB call PNG, -// we require PNG read all the IDATs and combine them into a single -// memory buffer - -typedef struct -{ - uint8 *zbuffer, *zbuffer_end; - int num_bits; - uint32 code_buffer; - - char *zout; - char *zout_start; - char *zout_end; - int z_expandable; - - zhuffman z_length, z_distance; -} zbuf; - -__forceinline static int zget8(zbuf *z) -{ - if (z->zbuffer >= z->zbuffer_end) return 0; - return *z->zbuffer++; -} - -static void fill_bits(zbuf *z) -{ - do { - assert(z->code_buffer < (1U << z->num_bits)); - z->code_buffer |= zget8(z) << z->num_bits; - z->num_bits += 8; - } while (z->num_bits <= 24); -} - -__forceinline static unsigned int zreceive(zbuf *z, int n) -{ - unsigned int k; - if (z->num_bits < n) fill_bits(z); - k = z->code_buffer & ((1 << n) - 1); - z->code_buffer >>= n; - z->num_bits -= n; - return k; -} - -__forceinline static int zhuffman_decode(zbuf *a, zhuffman *z) -{ - int b,s,k; - if (a->num_bits < 16) fill_bits(a); - b = z->fast[a->code_buffer & ZFAST_MASK]; - if (b < 0xffff) { - s = z->size[b]; - a->code_buffer >>= s; - a->num_bits -= s; - return z->value[b]; - } - - // not resolved by fast table, so compute it the slow way - // use jpeg approach, which requires MSbits at top - k = bit_reverse(a->code_buffer, 16); - for (s=ZFAST_BITS+1; ; ++s) - if (k < z->maxcode[s]) - break; - if (s == 16) return -1; // invalid code! - // code size is s, so: - b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; - assert(z->size[b] == s); - a->code_buffer >>= s; - a->num_bits -= s; - return z->value[b]; -} - -static int expand(zbuf *z, int n) // need to make room for n bytes -{ - char *q; - int cur, limit; - if (!z->z_expandable) return e("output buffer limit","Corrupt PNG"); - cur = (int) (z->zout - z->zout_start); - limit = (int) (z->zout_end - z->zout_start); - while (cur + n > limit) - limit *= 2; - q = (char *) realloc(z->zout_start, limit); - if (q == NULL) return e("outofmem", "Out of memory"); - z->zout_start = q; - z->zout = q + cur; - z->zout_end = q + limit; - return 1; -} - -static int length_base[31] = { - 3,4,5,6,7,8,9,10,11,13, - 15,17,19,23,27,31,35,43,51,59, - 67,83,99,115,131,163,195,227,258,0,0 }; - -static int length_extra[31]= -{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; - -static int dist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, -257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; - -static int dist_extra[32] = -{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; - -static int parse_huffman_block(zbuf *a) -{ - for(;;) { - int z = zhuffman_decode(a, &a->z_length); - if (z < 256) { - if (z < 0) return e("bad huffman code","Corrupt PNG"); // error in huffman codes - if (a->zout >= a->zout_end) if (!expand(a, 1)) return 0; - *a->zout++ = (char) z; - } else { - uint8 *p; - int len,dist; - if (z == 256) return 1; - z -= 257; - len = length_base[z]; - if (length_extra[z]) len += zreceive(a, length_extra[z]); - z = zhuffman_decode(a, &a->z_distance); - if (z < 0) return e("bad huffman code","Corrupt PNG"); - dist = dist_base[z]; - if (dist_extra[z]) dist += zreceive(a, dist_extra[z]); - if (a->zout - a->zout_start < dist) return e("bad dist","Corrupt PNG"); - if (a->zout + len > a->zout_end) if (!expand(a, len)) return 0; - p = (uint8 *) (a->zout - dist); - while (len--) - *a->zout++ = *p++; - } - } -} - -static int compute_huffman_codes(zbuf *a) -{ - static uint8 length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; - static zhuffman z_codelength; // static just to save stack space - uint8 lencodes[286+32+137];//padding for maximum single op - uint8 codelength_sizes[19]; - int i,n; - - int hlit = zreceive(a,5) + 257; - int hdist = zreceive(a,5) + 1; - int hclen = zreceive(a,4) + 4; - - memset(codelength_sizes, 0, sizeof(codelength_sizes)); - for (i=0; i < hclen; ++i) { - int s = zreceive(a,3); - codelength_sizes[length_dezigzag[i]] = (uint8) s; - } - if (!zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; - - n = 0; - while (n < hlit + hdist) { - int c = zhuffman_decode(a, &z_codelength); - assert(c >= 0 && c < 19); - if (c < 16) - lencodes[n++] = (uint8) c; - else if (c == 16) { - c = zreceive(a,2)+3; - memset(lencodes+n, lencodes[n-1], c); - n += c; - } else if (c == 17) { - c = zreceive(a,3)+3; - memset(lencodes+n, 0, c); - n += c; - } else { - assert(c == 18); - c = zreceive(a,7)+11; - memset(lencodes+n, 0, c); - n += c; - } - } - if (n != hlit+hdist) return e("bad codelengths","Corrupt PNG"); - if (!zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; - if (!zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; - return 1; -} - -static int parse_uncompressed_block(zbuf *a) -{ - uint8 header[4]; - int len,nlen,k; - if (a->num_bits & 7) - zreceive(a, a->num_bits & 7); // discard - // drain the bit-packed data into header - k = 0; - while (a->num_bits > 0) { - header[k++] = (uint8) (a->code_buffer & 255); // wtf this warns? - a->code_buffer >>= 8; - a->num_bits -= 8; - } - assert(a->num_bits == 0); - // now fill header the normal way - while (k < 4) - header[k++] = (uint8) zget8(a); - len = header[1] * 256 + header[0]; - nlen = header[3] * 256 + header[2]; - if (nlen != (len ^ 0xffff)) return e("zlib corrupt","Corrupt PNG"); - if (a->zbuffer + len > a->zbuffer_end) return e("read past buffer","Corrupt PNG"); - if (a->zout + len > a->zout_end) - if (!expand(a, len)) return 0; - memcpy(a->zout, a->zbuffer, len); - a->zbuffer += len; - a->zout += len; - return 1; -} - -static int parse_zlib_header(zbuf *a) -{ - int cmf = zget8(a); - int cm = cmf & 15; - /* int cinfo = cmf >> 4; */ - int flg = zget8(a); - if ((cmf*256+flg) % 31 != 0) return e("bad zlib header","Corrupt PNG"); // zlib spec - if (flg & 32) return e("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png - if (cm != 8) return e("bad compression","Corrupt PNG"); // DEFLATE required for png - // window = 1 << (8 + cinfo)... but who cares, we fully buffer output - return 1; -} - -// @TODO: should statically initialize these for optimal thread safety -static uint8 default_length[288], default_distance[32]; -static void init_defaults(void) -{ - int i; // use <= to match clearly with spec - for (i=0; i <= 143; ++i) default_length[i] = 8; - for ( ; i <= 255; ++i) default_length[i] = 9; - for ( ; i <= 279; ++i) default_length[i] = 7; - for ( ; i <= 287; ++i) default_length[i] = 8; - - for (i=0; i <= 31; ++i) default_distance[i] = 5; -} - -static int parse_zlib(zbuf *a, int parse_header) -{ - int final, type; - if (parse_header) - if (!parse_zlib_header(a)) return 0; - a->num_bits = 0; - a->code_buffer = 0; - do { - final = zreceive(a,1); - type = zreceive(a,2); - if (type == 0) { - if (!parse_uncompressed_block(a)) return 0; - } else if (type == 3) { - return 0; - } else { - if (type == 1) { - // use fixed code lengths - if (!default_distance[31]) init_defaults(); - if (!zbuild_huffman(&a->z_length , default_length , 288)) return 0; - if (!zbuild_huffman(&a->z_distance, default_distance, 32)) return 0; - } else { - if (!compute_huffman_codes(a)) return 0; - } - if (!parse_huffman_block(a)) return 0; - } - } while (!final); - return 1; -} - -static int do_zlib(zbuf *a, char *obuf, int olen, int exp, int parse_header) -{ - a->zout_start = obuf; - a->zout = obuf; - a->zout_end = obuf + olen; - a->z_expandable = exp; - - return parse_zlib(a, parse_header); -} - -char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) -{ - zbuf a; - char *p = (char *) malloc(initial_size); - if (p == NULL) return NULL; - a.zbuffer = (uint8 *) buffer; - a.zbuffer_end = (uint8 *) buffer + len; - if (do_zlib(&a, p, initial_size, 1, 1)) { - if (outlen) *outlen = (int) (a.zout - a.zout_start); - return a.zout_start; - } else { - free(a.zout_start); - return NULL; - } -} - -char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) -{ - return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); -} - -int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) -{ - zbuf a; - a.zbuffer = (uint8 *) ibuffer; - a.zbuffer_end = (uint8 *) ibuffer + ilen; - if (do_zlib(&a, obuffer, olen, 0, 1)) - return (int) (a.zout - a.zout_start); - else - return -1; -} - -char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) -{ - zbuf a; - char *p = (char *) malloc(16384); - if (p == NULL) return NULL; - a.zbuffer = (uint8 *) buffer; - a.zbuffer_end = (uint8 *) buffer+len; - if (do_zlib(&a, p, 16384, 1, 0)) { - if (outlen) *outlen = (int) (a.zout - a.zout_start); - return a.zout_start; - } else { - free(a.zout_start); - return NULL; - } -} - -int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) -{ - zbuf a; - a.zbuffer = (uint8 *) ibuffer; - a.zbuffer_end = (uint8 *) ibuffer + ilen; - if (do_zlib(&a, obuffer, olen, 0, 0)) - return (int) (a.zout - a.zout_start); - else - return -1; -} - -// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 -// simple implementation -// - only 8-bit samples -// - no CRC checking -// - allocates lots of intermediate memory -// - avoids problem of streaming data between subsystems -// - avoids explicit window management -// performance -// - uses stb_zlib, a PD zlib implementation with fast huffman decoding - - -typedef struct -{ - uint32 length; - uint32 type; -} chunk; - -#define PNG_TYPE(a,b,c,d) (((a) << 24) + ((b) << 16) + ((c) << 8) + (d)) - -static chunk get_chunk_header(stbi *s) -{ - chunk c; - c.length = get32(s); - c.type = get32(s); - return c; -} - -static int check_png_header(stbi *s) -{ - static uint8 png_sig[8] = { 137,80,78,71,13,10,26,10 }; - int i; - for (i=0; i < 8; ++i) - if (get8(s) != png_sig[i]) return e("bad png sig","Not a PNG"); - return 1; -} - -typedef struct -{ - stbi s; - uint8 *idata, *expanded, *out; -} png; - - -enum { - F_none=0, F_sub=1, F_up=2, F_avg=3, F_paeth=4, - F_avg_first, F_paeth_first, -}; - -static uint8 first_row_filter[5] = -{ - F_none, F_sub, F_none, F_avg_first, F_paeth_first -}; - -static int paeth(int a, int b, int c) -{ - int p = a + b - c; - int pa = abs(p-a); - int pb = abs(p-b); - int pc = abs(p-c); - if (pa <= pb && pa <= pc) return a; - if (pb <= pc) return b; - return c; -} - -// create the png data from post-deflated data -static int create_png_image(png *a, uint8 *raw, uint32 raw_len, int out_n) -{ - stbi *s = &a->s; - uint32 i,j,stride = s->img_x*out_n; - int k; - int img_n = s->img_n; // copy it into a local for later - assert(out_n == s->img_n || out_n == s->img_n+1); - a->out = (uint8 *) malloc(s->img_x * s->img_y * out_n); - if (!a->out) return e("outofmem", "Out of memory"); - if (raw_len != (img_n * s->img_x + 1) * s->img_y) return e("not enough pixels","Corrupt PNG"); - for (j=0; j < s->img_y; ++j) { - uint8 *cur = a->out + stride*j; - uint8 *prior = cur - stride; - int filter = *raw++; - if (filter > 4) return e("invalid filter","Corrupt PNG"); - // if first row, use special filter that doesn't sample previous row - if (j == 0) filter = first_row_filter[filter]; - // handle first pixel explicitly - for (k=0; k < img_n; ++k) { - switch(filter) { - case F_none : cur[k] = raw[k]; break; - case F_sub : cur[k] = raw[k]; break; - case F_up : cur[k] = raw[k] + prior[k]; break; - case F_avg : cur[k] = raw[k] + (prior[k]>>1); break; - case F_paeth : cur[k] = (uint8) (raw[k] + paeth(0,prior[k],0)); break; - case F_avg_first : cur[k] = raw[k]; break; - case F_paeth_first: cur[k] = raw[k]; break; - } - } - if (img_n != out_n) cur[img_n] = 255; - raw += img_n; - cur += out_n; - prior += out_n; - // this is a little gross, so that we don't switch per-pixel or per-component - if (img_n == out_n) { - #define CASE(f) \ - case f: \ - for (i=s->img_x-1; i >= 1; --i, raw+=img_n,cur+=img_n,prior+=img_n) \ - for (k=0; k < img_n; ++k) - switch(filter) { - CASE(F_none) cur[k] = raw[k]; break; - CASE(F_sub) cur[k] = raw[k] + cur[k-img_n]; break; - CASE(F_up) cur[k] = raw[k] + prior[k]; break; - CASE(F_avg) cur[k] = raw[k] + ((prior[k] + cur[k-img_n])>>1); break; - CASE(F_paeth) cur[k] = (uint8) (raw[k] + paeth(cur[k-img_n],prior[k],prior[k-img_n])); break; - CASE(F_avg_first) cur[k] = raw[k] + (cur[k-img_n] >> 1); break; - CASE(F_paeth_first) cur[k] = (uint8) (raw[k] + paeth(cur[k-img_n],0,0)); break; - } - #undef CASE - } else { - assert(img_n+1 == out_n); - #define CASE(f) \ - case f: \ - for (i=s->img_x-1; i >= 1; --i, cur[img_n]=255,raw+=img_n,cur+=out_n,prior+=out_n) \ - for (k=0; k < img_n; ++k) - switch(filter) { - CASE(F_none) cur[k] = raw[k]; break; - CASE(F_sub) cur[k] = raw[k] + cur[k-out_n]; break; - CASE(F_up) cur[k] = raw[k] + prior[k]; break; - CASE(F_avg) cur[k] = raw[k] + ((prior[k] + cur[k-out_n])>>1); break; - CASE(F_paeth) cur[k] = (uint8) (raw[k] + paeth(cur[k-out_n],prior[k],prior[k-out_n])); break; - CASE(F_avg_first) cur[k] = raw[k] + (cur[k-out_n] >> 1); break; - CASE(F_paeth_first) cur[k] = (uint8) (raw[k] + paeth(cur[k-out_n],0,0)); break; - } - #undef CASE - } - } - return 1; -} - -static int compute_transparency(png *z, uint8 tc[3], int out_n) -{ - stbi *s = &z->s; - uint32 i, pixel_count = s->img_x * s->img_y; - uint8 *p = z->out; - - // compute color-based transparency, assuming we've - // already got 255 as the alpha value in the output - assert(out_n == 2 || out_n == 4); - - if (out_n == 2) { - for (i=0; i < pixel_count; ++i) { - p[1] = (p[0] == tc[0] ? 0 : 255); - p += 2; - } - } else { - for (i=0; i < pixel_count; ++i) { - if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) - p[3] = 0; - p += 4; - } - } - return 1; -} - -static int expand_palette(png *a, uint8 *palette, int len, int pal_img_n) -{ - uint32 i, pixel_count = a->s.img_x * a->s.img_y; - uint8 *p, *temp_out, *orig = a->out; - - p = (uint8 *) malloc(pixel_count * pal_img_n); - if (p == NULL) return e("outofmem", "Out of memory"); - - // between here and free(out) below, exitting would leak - temp_out = p; - - if (pal_img_n == 3) { - for (i=0; i < pixel_count; ++i) { - int n = orig[i]*4; - p[0] = palette[n ]; - p[1] = palette[n+1]; - p[2] = palette[n+2]; - p += 3; - } - } else { - for (i=0; i < pixel_count; ++i) { - int n = orig[i]*4; - p[0] = palette[n ]; - p[1] = palette[n+1]; - p[2] = palette[n+2]; - p[3] = palette[n+3]; - p += 4; - } - } - free(a->out); - a->out = temp_out; - return 1; -} - -static int parse_png_file(png *z, int scan, int req_comp) -{ - uint8 palette[1024], pal_img_n=0; - uint8 has_trans=0, tc[3]; - uint32 ioff=0, idata_limit=0, i, pal_len=0; - int first=1,k; - stbi *s = &z->s; - - if (!check_png_header(s)) return 0; - - if (scan == SCAN_type) return 1; - - for(;;first=0) { - chunk c = get_chunk_header(s); - if (first && c.type != PNG_TYPE('I','H','D','R')) - return e("first not IHDR","Corrupt PNG"); - switch (c.type) { - case PNG_TYPE('I','H','D','R'): { - int depth,color,interlace,comp,filter; - if (!first) return e("multiple IHDR","Corrupt PNG"); - if (c.length != 13) return e("bad IHDR len","Corrupt PNG"); - s->img_x = get32(s); if (s->img_x > (1 << 24)) return e("too large","Very large image (corrupt?)"); - s->img_y = get32(s); if (s->img_y > (1 << 24)) return e("too large","Very large image (corrupt?)"); - depth = get8(s); if (depth != 8) return e("8bit only","PNG not supported: 8-bit only"); - color = get8(s); if (color > 6) return e("bad ctype","Corrupt PNG"); - if (color == 3) pal_img_n = 3; else if (color & 1) return e("bad ctype","Corrupt PNG"); - comp = get8(s); if (comp) return e("bad comp method","Corrupt PNG"); - filter= get8(s); if (filter) return e("bad filter method","Corrupt PNG"); - interlace = get8(s); if (interlace) return e("interlaced","PNG not supported: interlaced mode"); - if (!s->img_x || !s->img_y) return e("0-pixel image","Corrupt PNG"); - if (!pal_img_n) { - s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); - if ((1 << 30) / s->img_x / s->img_n < s->img_y) return e("too large", "Image too large to decode"); - if (scan == SCAN_header) return 1; - } else { - // if paletted, then pal_n is our final components, and - // img_n is # components to decompress/filter. - s->img_n = 1; - if ((1 << 30) / s->img_x / 4 < s->img_y) return e("too large","Corrupt PNG"); - // if SCAN_header, have to scan to see if we have a tRNS - } - break; - } - - case PNG_TYPE('P','L','T','E'): { - if (c.length > 256*3) return e("invalid PLTE","Corrupt PNG"); - pal_len = c.length / 3; - if (pal_len * 3 != c.length) return e("invalid PLTE","Corrupt PNG"); - for (i=0; i < pal_len; ++i) { - palette[i*4+0] = get8u(s); - palette[i*4+1] = get8u(s); - palette[i*4+2] = get8u(s); - palette[i*4+3] = 255; - } - break; - } - - case PNG_TYPE('t','R','N','S'): { - if (z->idata) return e("tRNS after IDAT","Corrupt PNG"); - if (pal_img_n) { - if (scan == SCAN_header) { s->img_n = 4; return 1; } - if (pal_len == 0) return e("tRNS before PLTE","Corrupt PNG"); - if (c.length > pal_len) return e("bad tRNS len","Corrupt PNG"); - pal_img_n = 4; - for (i=0; i < c.length; ++i) - palette[i*4+3] = get8u(s); - } else { - if (!(s->img_n & 1)) return e("tRNS with alpha","Corrupt PNG"); - if (c.length != (uint32) s->img_n*2) return e("bad tRNS len","Corrupt PNG"); - has_trans = 1; - for (k=0; k < s->img_n; ++k) - tc[k] = (uint8) get16(s); // non 8-bit images will be larger - } - break; - } - - case PNG_TYPE('I','D','A','T'): { - if (pal_img_n && !pal_len) return e("no PLTE","Corrupt PNG"); - if (scan == SCAN_header) { s->img_n = pal_img_n; return 1; } - if (ioff + c.length > idata_limit) { - uint8 *p; - if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; - while (ioff + c.length > idata_limit) - idata_limit *= 2; - p = (uint8 *) realloc(z->idata, idata_limit); if (p == NULL) return e("outofmem", "Out of memory"); - z->idata = p; - } - #ifndef STBI_NO_STDIO - if (s->img_file) - { - if (fread(z->idata+ioff,1,c.length,s->img_file) != c.length) return e("outofdata","Corrupt PNG"); - } - else - #endif - { - memcpy(z->idata+ioff, s->img_buffer, c.length); - s->img_buffer += c.length; - } - ioff += c.length; - break; - } - - case PNG_TYPE('I','E','N','D'): { - uint32 raw_len; - if (scan != SCAN_load) return 1; - if (z->idata == NULL) return e("no IDAT","Corrupt PNG"); - z->expanded = (uint8 *) stbi_zlib_decode_malloc((char *) z->idata, ioff, (int *) &raw_len); - if (z->expanded == NULL) return 0; // zlib should set error - free(z->idata); z->idata = NULL; - if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) - s->img_out_n = s->img_n+1; - else - s->img_out_n = s->img_n; - if (!create_png_image(z, z->expanded, raw_len, s->img_out_n)) return 0; - if (has_trans) - if (!compute_transparency(z, tc, s->img_out_n)) return 0; - if (pal_img_n) { - // pal_img_n == 3 or 4 - s->img_n = pal_img_n; // record the actual colors we had - s->img_out_n = pal_img_n; - if (req_comp >= 3) s->img_out_n = req_comp; - if (!expand_palette(z, palette, pal_len, s->img_out_n)) - return 0; - } - free(z->expanded); z->expanded = NULL; - return 1; - } - - default: - // if critical, fail - if ((c.type & (1 << 29)) == 0) { - #ifndef STBI_NO_FAILURE_STRINGS - // not threadsafe - static char invalid_chunk[] = "XXXX chunk not known"; - invalid_chunk[0] = (uint8) (c.type >> 24); - invalid_chunk[1] = (uint8) (c.type >> 16); - invalid_chunk[2] = (uint8) (c.type >> 8); - invalid_chunk[3] = (uint8) (c.type >> 0); - #endif - return e(invalid_chunk, "PNG not supported: unknown chunk type"); - } - skip(s, c.length); - break; - } - // end of chunk, read and skip CRC - get32(s); - } -} - -static unsigned char *do_png(png *p, int *x, int *y, int *n, int req_comp) -{ - unsigned char *result=NULL; - p->expanded = NULL; - p->idata = NULL; - p->out = NULL; - if (req_comp < 0 || req_comp > 4) return epuc("bad req_comp", "Internal error"); - if (parse_png_file(p, SCAN_load, req_comp)) { - result = p->out; - p->out = NULL; - if (req_comp && req_comp != p->s.img_out_n) { - result = convert_format(result, p->s.img_out_n, req_comp, p->s.img_x, p->s.img_y); - p->s.img_out_n = req_comp; - if (result == NULL) return result; - } - *x = p->s.img_x; - *y = p->s.img_y; - if (n) *n = p->s.img_n; - } - free(p->out); p->out = NULL; - free(p->expanded); p->expanded = NULL; - free(p->idata); p->idata = NULL; - - return result; -} - -#ifndef STBI_NO_STDIO -unsigned char *stbi_png_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) -{ - png p; - start_file(&p.s, f); - return do_png(&p, x,y,comp,req_comp); -} - -unsigned char *stbi_png_load(char const *filename, int *x, int *y, int *comp, int req_comp) -{ - unsigned char *data; - FILE *f = fopen(filename, "rb"); - if (!f) return NULL; - data = stbi_png_load_from_file(f,x,y,comp,req_comp); - fclose(f); - return data; -} -#endif - -unsigned char *stbi_png_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) -{ - png p; - start_mem(&p.s, buffer,len); - return do_png(&p, x,y,comp,req_comp); -} - -#ifndef STBI_NO_STDIO -int stbi_png_test_file(FILE *f) -{ - png p; - int n,r; - n = ftell(f); - start_file(&p.s, f); - r = parse_png_file(&p, SCAN_type,STBI_default); - fseek(f,n,SEEK_SET); - return r; -} -#endif - -int stbi_png_test_memory(stbi_uc const *buffer, int len) -{ - png p; - start_mem(&p.s, buffer, len); - return parse_png_file(&p, SCAN_type,STBI_default); -} - -// TODO: load header from png -#ifndef STBI_NO_STDIO -extern int stbi_png_info (char const *filename, int *x, int *y, int *comp); -extern int stbi_png_info_from_file (FILE *f, int *x, int *y, int *comp); -#endif -extern int stbi_png_info_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp); - -// Microsoft/Windows BMP image - -static int bmp_test(stbi *s) -{ - int sz; - if (get8(s) != 'B') return 0; - if (get8(s) != 'M') return 0; - get32le(s); // discard filesize - get16le(s); // discard reserved - get16le(s); // discard reserved - get32le(s); // discard data offset - sz = get32le(s); - if (sz == 12 || sz == 40 || sz == 56 || sz == 108) return 1; - return 0; -} - -#ifndef STBI_NO_STDIO -int stbi_bmp_test_file (FILE *f) -{ - stbi s; - int r,n = ftell(f); - start_file(&s,f); - r = bmp_test(&s); - fseek(f,n,SEEK_SET); - return r; -} -#endif - -int stbi_bmp_test_memory (stbi_uc const *buffer, int len) -{ - stbi s; - start_mem(&s, buffer, len); - return bmp_test(&s); -} - -// returns 0..31 for the highest set bit -static int high_bit(unsigned int z) -{ - int n=0; - if (z == 0) return -1; - if (z >= 0x10000) n += 16, z >>= 16; - if (z >= 0x00100) n += 8, z >>= 8; - if (z >= 0x00010) n += 4, z >>= 4; - if (z >= 0x00004) n += 2, z >>= 2; - if (z >= 0x00002) n += 1, z >>= 1; - return n; -} - -static int bitcount(unsigned int a) -{ - a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 - a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 - a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits - a = (a + (a >> 8)); // max 16 per 8 bits - a = (a + (a >> 16)); // max 32 per 8 bits - return a & 0xff; -} - -static int shiftsigned(int v, int shift, int bits) -{ - int result; - int z=0; - - if (shift < 0) v <<= -shift; - else v >>= shift; - result = v; - - z = bits; - while (z < 8) { - result += v >> z; - z += bits; - } - return result; -} - -static stbi_uc *bmp_load(stbi *s, int *x, int *y, int *comp, int req_comp) -{ - uint8 *out; - unsigned int mr=0,mg=0,mb=0,ma=0; - stbi_uc pal[256][4]; - int psize=0,i,j,compress=0,width; - int bpp, flip_vertically, pad, target, offset, hsz; - if (get8(s) != 'B' || get8(s) != 'M') return epuc("not BMP", "Corrupt BMP"); - get32le(s); // discard filesize - get16le(s); // discard reserved - get16le(s); // discard reserved - offset = get32le(s); - hsz = get32le(s); - if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108) return epuc("unknown BMP", "BMP type not supported: unknown"); - failure_reason = "bad BMP"; - if (hsz == 12) { - s->img_x = get16le(s); - s->img_y = get16le(s); - } else { - s->img_x = get32le(s); - s->img_y = get32le(s); - } - if (get16le(s) != 1) return 0; - bpp = get16le(s); - if (bpp == 1) return epuc("monochrome", "BMP type not supported: 1-bit"); - flip_vertically = ((int) s->img_y) > 0; - s->img_y = abs((int) s->img_y); - if (hsz == 12) { - if (bpp < 24) - psize = (offset - 14 - 24) / 3; - } else { - compress = get32le(s); - if (compress == 1 || compress == 2) return epuc("BMP RLE", "BMP type not supported: RLE"); - get32le(s); // discard sizeof - get32le(s); // discard hres - get32le(s); // discard vres - get32le(s); // discard colorsused - get32le(s); // discard max important - if (hsz == 40 || hsz == 56) { - if (hsz == 56) { - get32le(s); - get32le(s); - get32le(s); - get32le(s); - } - if (bpp == 16 || bpp == 32) { - mr = mg = mb = 0; - if (compress == 0) { - if (bpp == 32) { - mr = 0xff << 16; - mg = 0xff << 8; - mb = 0xff << 0; - } else { - mr = 31 << 10; - mg = 31 << 5; - mb = 31 << 0; - } - } else if (compress == 3) { - mr = get32le(s); - mg = get32le(s); - mb = get32le(s); - // not documented, but generated by photoshop and handled by mspaint - if (mr == mg && mg == mb) { - // ?!?!? - return NULL; - } - } else - return NULL; - } - } else { - assert(hsz == 108); - mr = get32le(s); - mg = get32le(s); - mb = get32le(s); - ma = get32le(s); - get32le(s); // discard color space - for (i=0; i < 12; ++i) - get32le(s); // discard color space parameters - } - if (bpp < 16) - psize = (offset - 14 - hsz) >> 2; - } - s->img_n = ma ? 4 : 3; - if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 - target = req_comp; - else - target = s->img_n; // if they want monochrome, we'll post-convert - out = (stbi_uc *) malloc(target * s->img_x * s->img_y); - if (!out) return epuc("outofmem", "Out of memory"); - if (bpp < 16) { - int z=0; - if (psize == 0 || psize > 256) { free(out); return epuc("invalid", "Corrupt BMP"); } - for (i=0; i < psize; ++i) { - pal[i][2] = get8(s); - pal[i][1] = get8(s); - pal[i][0] = get8(s); - if (hsz != 12) get8(s); - pal[i][3] = 255; - } - skip(s, offset - 14 - hsz - psize * (hsz == 12 ? 3 : 4)); - if (bpp == 4) width = (s->img_x + 1) >> 1; - else if (bpp == 8) width = s->img_x; - else { free(out); return epuc("bad bpp", "Corrupt BMP"); } - pad = (-width)&3; - for (j=0; j < (int) s->img_y; ++j) { - for (i=0; i < (int) s->img_x; i += 2) { - int v=get8(s),v2=0; - if (bpp == 4) { - v2 = v & 15; - v >>= 4; - } - out[z++] = pal[v][0]; - out[z++] = pal[v][1]; - out[z++] = pal[v][2]; - if (target == 4) out[z++] = 255; - if (i+1 == (int) s->img_x) break; - v = (bpp == 8) ? get8(s) : v2; - out[z++] = pal[v][0]; - out[z++] = pal[v][1]; - out[z++] = pal[v][2]; - if (target == 4) out[z++] = 255; - } - skip(s, pad); - } - } else { - int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; - int z = 0; - int easy=0; - skip(s, offset - 14 - hsz); - if (bpp == 24) width = 3 * s->img_x; - else if (bpp == 16) width = 2*s->img_x; - else /* bpp = 32 and pad = 0 */ width=0; - pad = (-width) & 3; - if (bpp == 24) { - easy = 1; - } else if (bpp == 32) { - if (mb == 0xff && mg == 0xff00 && mr == 0xff000000 && ma == 0xff000000) - easy = 2; - } - if (!easy) { - if (!mr || !mg || !mb) return epuc("bad masks", "Corrupt BMP"); - // right shift amt to put high bit in position #7 - rshift = high_bit(mr)-7; rcount = bitcount(mr); - gshift = high_bit(mg)-7; gcount = bitcount(mr); - bshift = high_bit(mb)-7; bcount = bitcount(mr); - ashift = high_bit(ma)-7; acount = bitcount(mr); - } - for (j=0; j < (int) s->img_y; ++j) { - if (easy) { - for (i=0; i < (int) s->img_x; ++i) { - int a; - out[z+2] = get8(s); - out[z+1] = get8(s); - out[z+0] = get8(s); - z += 3; - a = (easy == 2 ? get8(s) : 255); - if (target == 4) out[z++] = a; - } - } else { - for (i=0; i < (int) s->img_x; ++i) { - uint32 v = (bpp == 16 ? get16le(s) : get32le(s)); - int a; - out[z++] = shiftsigned(v & mr, rshift, rcount); - out[z++] = shiftsigned(v & mg, gshift, gcount); - out[z++] = shiftsigned(v & mb, bshift, bcount); - a = (ma ? shiftsigned(v & ma, ashift, acount) : 255); - if (target == 4) out[z++] = a; - } - } - skip(s, pad); - } - } - if (flip_vertically) { - stbi_uc t; - for (j=0; j < (int) s->img_y>>1; ++j) { - stbi_uc *p1 = out + j *s->img_x*target; - stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; - for (i=0; i < (int) s->img_x*target; ++i) { - t = p1[i], p1[i] = p2[i], p2[i] = t; - } - } - } - - if (req_comp && req_comp != target) { - out = convert_format(out, target, req_comp, s->img_x, s->img_y); - if (out == NULL) return out; // convert_format frees input on failure - } - - *x = s->img_x; - *y = s->img_y; - if (comp) *comp = target; - return out; -} - -#ifndef STBI_NO_STDIO -stbi_uc *stbi_bmp_load (char const *filename, int *x, int *y, int *comp, int req_comp) -{ - stbi_uc *data; - FILE *f = fopen(filename, "rb"); - if (!f) return NULL; - data = stbi_bmp_load_from_file(f, x,y,comp,req_comp); - fclose(f); - return data; -} - -stbi_uc *stbi_bmp_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp) -{ - stbi s; - start_file(&s, f); - return bmp_load(&s, x,y,comp,req_comp); -} -#endif - -stbi_uc *stbi_bmp_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) -{ - stbi s; - start_mem(&s, buffer, len); - return bmp_load(&s, x,y,comp,req_comp); -} - -// Targa Truevision - TGA -// by Jonathan Dummer - -static int tga_test(stbi *s) -{ - int sz; - get8u(s); // discard Offset - sz = get8u(s); // color type - if( sz > 1 ) return 0; // only RGB or indexed allowed - sz = get8u(s); // image type - if( (sz != 1) && (sz != 2) && (sz != 3) && (sz != 9) && (sz != 10) && (sz != 11) ) return 0; // only RGB or grey allowed, +/- RLE - get16(s); // discard palette start - get16(s); // discard palette length - get8(s); // discard bits per palette color entry - get16(s); // discard x origin - get16(s); // discard y origin - if( get16(s) < 1 ) return 0; // test width - if( get16(s) < 1 ) return 0; // test height - sz = get8(s); // bits per pixel - if( (sz != 8) && (sz != 16) && (sz != 24) && (sz != 32) ) return 0; // only RGB or RGBA or grey allowed - return 1; // seems to have passed everything -} - -#ifndef STBI_NO_STDIO -int stbi_tga_test_file (FILE *f) -{ - stbi s; - int r,n = ftell(f); - start_file(&s, f); - r = tga_test(&s); - fseek(f,n,SEEK_SET); - return r; -} -#endif - -int stbi_tga_test_memory (stbi_uc const *buffer, int len) -{ - stbi s; - start_mem(&s, buffer, len); - return tga_test(&s); -} - -static stbi_uc *tga_load(stbi *s, int *x, int *y, int *comp, int req_comp) -{ - // read in the TGA header stuff - int tga_offset = get8u(s); - int tga_indexed = get8u(s); - int tga_image_type = get8u(s); - int tga_is_RLE = 0; - int tga_palette_start = get16le(s); - int tga_palette_len = get16le(s); - int tga_palette_bits = get8u(s); - int tga_x_origin = get16le(s); - int tga_y_origin = get16le(s); - int tga_width = get16le(s); - int tga_height = get16le(s); - int tga_bits_per_pixel = get8u(s); - int tga_inverted = get8u(s); - // image data - unsigned char *tga_data; - unsigned char *tga_palette = NULL; - int i, j; - unsigned char raw_data[4]; - unsigned char trans_data[] = { 0,0,0,0 }; - int RLE_count = 0; - int RLE_repeating = 0; - int read_next_pixel = 1; - // do a tiny bit of precessing - if( tga_image_type >= 8 ) - { - tga_image_type -= 8; - tga_is_RLE = 1; - } - /* int tga_alpha_bits = tga_inverted & 15; */ - tga_inverted = 1 - ((tga_inverted >> 5) & 1); - - // error check - if( //(tga_indexed) || - (tga_width < 1) || (tga_height < 1) || - (tga_image_type < 1) || (tga_image_type > 3) || - ((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16) && - (tga_bits_per_pixel != 24) && (tga_bits_per_pixel != 32)) - ) - { - return NULL; - } - - // If I'm paletted, then I'll use the number of bits from the palette - if( tga_indexed ) - { - tga_bits_per_pixel = tga_palette_bits; - } - - // tga info - *x = tga_width; - *y = tga_height; - if( (req_comp < 1) || (req_comp > 4) ) - { - // just use whatever the file was - req_comp = tga_bits_per_pixel / 8; - *comp = req_comp; - } else - { - // force a new number of components - *comp = tga_bits_per_pixel/8; - } - tga_data = (unsigned char*)malloc( tga_width * tga_height * req_comp ); - - // skip to the data's starting position (offset usually = 0) - skip(s, tga_offset ); - // do I need to load a palette? - if( tga_indexed ) - { - // any data to skip? (offset usually = 0) - skip(s, tga_palette_start ); - // load the palette - tga_palette = (unsigned char*)malloc( tga_palette_len * tga_palette_bits / 8 ); - getn(s, tga_palette, tga_palette_len * tga_palette_bits / 8 ); - } - // load the data - for( i = 0; i < tga_width * tga_height; ++i ) - { - // if I'm in RLE mode, do I need to get a RLE chunk? - if( tga_is_RLE ) - { - if( RLE_count == 0 ) - { - // yep, get the next byte as a RLE command - int RLE_cmd = get8u(s); - RLE_count = 1 + (RLE_cmd & 127); - RLE_repeating = RLE_cmd >> 7; - read_next_pixel = 1; - } else if( !RLE_repeating ) - { - read_next_pixel = 1; - } - } else - { - read_next_pixel = 1; - } - // OK, if I need to read a pixel, do it now - if( read_next_pixel ) - { - // load however much data we did have - if( tga_indexed ) - { - // read in 1 byte, then perform the lookup - int pal_idx = get8u(s); - if( pal_idx >= tga_palette_len ) - { - // invalid index - pal_idx = 0; - } - pal_idx *= tga_bits_per_pixel / 8; - for( j = 0; j*8 < tga_bits_per_pixel; ++j ) - { - raw_data[j] = tga_palette[pal_idx+j]; - } - } else - { - // read in the data raw - for( j = 0; j*8 < tga_bits_per_pixel; ++j ) - { - raw_data[j] = get8u(s); - } - } - // convert raw to the intermediate format - switch( tga_bits_per_pixel ) - { - case 8: - // Luminous => RGBA - trans_data[0] = raw_data[0]; - trans_data[1] = raw_data[0]; - trans_data[2] = raw_data[0]; - trans_data[3] = 255; - break; - case 16: - // Luminous,Alpha => RGBA - trans_data[0] = raw_data[0]; - trans_data[1] = raw_data[0]; - trans_data[2] = raw_data[0]; - trans_data[3] = raw_data[1]; - break; - case 24: - // BGR => RGBA - trans_data[0] = raw_data[2]; - trans_data[1] = raw_data[1]; - trans_data[2] = raw_data[0]; - trans_data[3] = 255; - break; - case 32: - // BGRA => RGBA - trans_data[0] = raw_data[2]; - trans_data[1] = raw_data[1]; - trans_data[2] = raw_data[0]; - trans_data[3] = raw_data[3]; - break; - } - // clear the reading flag for the next pixel - read_next_pixel = 0; - } // end of reading a pixel - // convert to final format - switch( req_comp ) - { - case 1: - // RGBA => Luminance - tga_data[i*req_comp+0] = compute_y(trans_data[0],trans_data[1],trans_data[2]); - break; - case 2: - // RGBA => Luminance,Alpha - tga_data[i*req_comp+0] = compute_y(trans_data[0],trans_data[1],trans_data[2]); - tga_data[i*req_comp+1] = trans_data[3]; - break; - case 3: - // RGBA => RGB - tga_data[i*req_comp+0] = trans_data[0]; - tga_data[i*req_comp+1] = trans_data[1]; - tga_data[i*req_comp+2] = trans_data[2]; - break; - case 4: - // RGBA => RGBA - tga_data[i*req_comp+0] = trans_data[0]; - tga_data[i*req_comp+1] = trans_data[1]; - tga_data[i*req_comp+2] = trans_data[2]; - tga_data[i*req_comp+3] = trans_data[3]; - break; - } - // in case we're in RLE mode, keep counting down - --RLE_count; - } - // do I need to invert the image? - if( tga_inverted ) - { - for( j = 0; j*2 < tga_height; ++j ) - { - int index1 = j * tga_width * req_comp; - int index2 = (tga_height - 1 - j) * tga_width * req_comp; - for( i = tga_width * req_comp; i > 0; --i ) - { - unsigned char temp = tga_data[index1]; - tga_data[index1] = tga_data[index2]; - tga_data[index2] = temp; - ++index1; - ++index2; - } - } - } - // clear my palette, if I had one - if( tga_palette != NULL ) - { - free( tga_palette ); - } - // the things I do to get rid of an error message, and yet keep - // Microsoft's C compilers happy... [8^( - tga_palette_start = tga_palette_len = tga_palette_bits = - tga_x_origin = tga_y_origin = 0; - // OK, done - return tga_data; -} - -#ifndef STBI_NO_STDIO -stbi_uc *stbi_tga_load (char const *filename, int *x, int *y, int *comp, int req_comp) -{ - stbi_uc *data; - FILE *f = fopen(filename, "rb"); - if (!f) return NULL; - data = stbi_tga_load_from_file(f, x,y,comp,req_comp); - fclose(f); - return data; -} - -stbi_uc *stbi_tga_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp) -{ - stbi s; - start_file(&s, f); - return tga_load(&s, x,y,comp,req_comp); -} -#endif - -stbi_uc *stbi_tga_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) -{ - stbi s; - start_mem(&s, buffer, len); - return tga_load(&s, x,y,comp,req_comp); -} - - -// ************************************************************************************************* -// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicholas Schulz, tweaked by STB - -static int psd_test(stbi *s) -{ - if (get32(s) != 0x38425053) return 0; // "8BPS" - else return 1; -} - -#ifndef STBI_NO_STDIO -int stbi_psd_test_file(FILE *f) -{ - stbi s; - int r,n = ftell(f); - start_file(&s, f); - r = psd_test(&s); - fseek(f,n,SEEK_SET); - return r; -} -#endif - -int stbi_psd_test_memory(stbi_uc const *buffer, int len) -{ - stbi s; - start_mem(&s, buffer, len); - return psd_test(&s); -} - -static stbi_uc *psd_load(stbi *s, int *x, int *y, int *comp, int req_comp) -{ - int pixelCount; - int channelCount, compression; - int channel, i, count, len; - int w,h; - uint8 *out; - - // Check identifier - if (get32(s) != 0x38425053) // "8BPS" - return epuc("not PSD", "Corrupt PSD image"); - - // Check file type version. - if (get16(s) != 1) - return epuc("wrong version", "Unsupported version of PSD image"); - - // Skip 6 reserved bytes. - skip(s, 6 ); - - // Read the number of channels (R, G, B, A, etc). - channelCount = get16(s); - if (channelCount < 0 || channelCount > 16) - return epuc("wrong channel count", "Unsupported number of channels in PSD image"); - - // Read the rows and columns of the image. - h = get32(s); - w = get32(s); - - // Make sure the depth is 8 bits. - if (get16(s) != 8) - return epuc("unsupported bit depth", "PSD bit depth is not 8 bit"); - - // Make sure the color mode is RGB. - // Valid options are: - // 0: Bitmap - // 1: Grayscale - // 2: Indexed color - // 3: RGB color - // 4: CMYK color - // 7: Multichannel - // 8: Duotone - // 9: Lab color - if (get16(s) != 3) - return epuc("wrong color format", "PSD is not in RGB color format"); - - // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) - skip(s,get32(s) ); - - // Skip the image resources. (resolution, pen tool paths, etc) - skip(s, get32(s) ); - - // Skip the reserved data. - skip(s, get32(s) ); - - // Find out if the data is compressed. - // Known values: - // 0: no compression - // 1: RLE compressed - compression = get16(s); - if (compression > 1) - return epuc("bad compression", "PSD has an unknown compression format"); - - // Create the destination image. - out = (stbi_uc *) malloc(4 * w*h); - if (!out) return epuc("outofmem", "Out of memory"); - pixelCount = w*h; - - // Initialize the data to zero. - //memset( out, 0, pixelCount * 4 ); - - // Finally, the image data. - if (compression) { - // RLE as used by .PSD and .TIFF - // Loop until you get the number of unpacked bytes you are expecting: - // Read the next source byte into n. - // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. - // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. - // Else if n is 128, noop. - // Endloop - - // The RLE-compressed data is preceeded by a 2-byte data count for each row in the data, - // which we're going to just skip. - skip(s, h * channelCount * 2 ); - - // Read the RLE data by channel. - for (channel = 0; channel < 4; channel++) { - uint8 *p; - - p = out+channel; - if (channel >= channelCount) { - // Fill this channel with default data. - for (i = 0; i < pixelCount; i++) *p = (channel == 3 ? 255 : 0), p += 4; - } else { - // Read the RLE data. - count = 0; - while (count < pixelCount) { - len = get8(s); - if (len == 128) { - // No-op. - } else if (len < 128) { - // Copy next len+1 bytes literally. - len++; - count += len; - while (len) { - *p = get8(s); - p += 4; - len--; - } - } else if (len > 128) { - uint32 val; - // Next -len+1 bytes in the dest are replicated from next source byte. - // (Interpret len as a negative 8-bit int.) - len ^= 0x0FF; - len += 2; - val = get8(s); - count += len; - while (len) { - *p = val; - p += 4; - len--; - } - } - } - } - } - - } else { - // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) - // where each channel consists of an 8-bit value for each pixel in the image. - - // Read the data by channel. - for (channel = 0; channel < 4; channel++) { - uint8 *p; - - p = out + channel; - if (channel > channelCount) { - // Fill this channel with default data. - for (i = 0; i < pixelCount; i++) *p = channel == 3 ? 255 : 0, p += 4; - } else { - // Read the data. - count = 0; - for (i = 0; i < pixelCount; i++) - *p = get8(s), p += 4; - } - } - } - - if (req_comp && req_comp != 4) { - out = convert_format(out, 4, req_comp, w, h); - if (out == NULL) return out; // convert_format frees input on failure - } - - if (comp) *comp = channelCount; - *y = h; - *x = w; - - return out; -} - -#ifndef STBI_NO_STDIO -stbi_uc *stbi_psd_load(char const *filename, int *x, int *y, int *comp, int req_comp) -{ - stbi_uc *data; - FILE *f = fopen(filename, "rb"); - if (!f) return NULL; - data = stbi_psd_load_from_file(f, x,y,comp,req_comp); - fclose(f); - return data; -} - -stbi_uc *stbi_psd_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) -{ - stbi s; - start_file(&s, f); - return psd_load(&s, x,y,comp,req_comp); -} -#endif - -stbi_uc *stbi_psd_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) -{ - stbi s; - start_mem(&s, buffer, len); - return psd_load(&s, x,y,comp,req_comp); -} - - -// ************************************************************************************************* -// Radiance RGBE HDR loader -// originally by Nicolas Schulz -#ifndef STBI_NO_HDR -static int hdr_test(stbi *s) -{ - char *signature = "#?RADIANCE\n"; - int i; - for (i=0; signature[i]; ++i) - if (get8(s) != signature[i]) - return 0; - return 1; -} - -int stbi_hdr_test_memory(stbi_uc const *buffer, int len) -{ - stbi s; - start_mem(&s, buffer, len); - return hdr_test(&s); -} - -#ifndef STBI_NO_STDIO -int stbi_hdr_test_file(FILE *f) -{ - stbi s; - int r,n = ftell(f); - start_file(&s, f); - r = hdr_test(&s); - fseek(f,n,SEEK_SET); - return r; -} -#endif - -#define HDR_BUFLEN 1024 -static char *hdr_gettoken(stbi *z, char *buffer) -{ - int len=0; - //char *s = buffer, - char c = '\0'; - - c = get8(z); - - while (!at_eof(z) && c != '\n') { - buffer[len++] = c; - if (len == HDR_BUFLEN-1) { - // flush to end of line - while (!at_eof(z) && get8(z) != '\n') - ; - break; - } - c = get8(z); - } - - buffer[len] = 0; - return buffer; -} - -static void hdr_convert(float *output, stbi_uc *input, int req_comp) -{ - if( input[3] != 0 ) { - float f1; - // Exponent - f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); - if (req_comp <= 2) - output[0] = (input[0] + input[1] + input[2]) * f1 / 3; - else { - output[0] = input[0] * f1; - output[1] = input[1] * f1; - output[2] = input[2] * f1; - } - if (req_comp == 2) output[1] = 1; - if (req_comp == 4) output[3] = 1; - } else { - switch (req_comp) { - case 4: output[3] = 1; /* fallthrough */ - case 3: output[0] = output[1] = output[2] = 0; - break; - case 2: output[1] = 1; /* fallthrough */ - case 1: output[0] = 0; - break; - } - } -} - - -static float *hdr_load(stbi *s, int *x, int *y, int *comp, int req_comp) -{ - char buffer[HDR_BUFLEN]; - char *token; - int valid = 0; - int width, height; - stbi_uc *scanline; - float *hdr_data; - int len; - unsigned char count, value; - int i, j, k, c1,c2, z; - - - // Check identifier - if (strcmp(hdr_gettoken(s,buffer), "#?RADIANCE") != 0) - return epf("not HDR", "Corrupt HDR image"); - - // Parse header - while(1) { - token = hdr_gettoken(s,buffer); - if (token[0] == 0) break; - if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; - } - - if (!valid) return epf("unsupported format", "Unsupported HDR format"); - - // Parse width and height - // can't use sscanf() if we're not using stdio! - token = hdr_gettoken(s,buffer); - if (strncmp(token, "-Y ", 3)) return epf("unsupported data layout", "Unsupported HDR format"); - token += 3; - height = strtol(token, &token, 10); - while (*token == ' ') ++token; - if (strncmp(token, "+X ", 3)) return epf("unsupported data layout", "Unsupported HDR format"); - token += 3; - width = strtol(token, NULL, 10); - - *x = width; - *y = height; - - *comp = 3; - if (req_comp == 0) req_comp = 3; - - // Read data - hdr_data = (float *) malloc(height * width * req_comp * sizeof(float)); - - // Load image data - // image data is stored as some number of sca - if( width < 8 || width >= 32768) { - // Read flat data - for (j=0; j < height; ++j) { - for (i=0; i < width; ++i) { - stbi_uc rgbe[4]; - main_decode_loop: - getn(s, rgbe, 4); - hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); - } - } - } else { - // Read RLE-encoded data - scanline = NULL; - - for (j = 0; j < height; ++j) { - c1 = get8(s); - c2 = get8(s); - len = get8(s); - if (c1 != 2 || c2 != 2 || (len & 0x80)) { - // not run-length encoded, so we have to actually use THIS data as a decoded - // pixel (note this can't be a valid pixel--one of RGB must be >= 128) - stbi_uc rgbe[4] = { c1,c2,len, get8(s) }; - hdr_convert(hdr_data, rgbe, req_comp); - i = 1; - j = 0; - free(scanline); - goto main_decode_loop; // yes, this is fucking insane; blame the fucking insane format - } - len <<= 8; - len |= get8(s); - if (len != width) { free(hdr_data); free(scanline); return epf("invalid decoded scanline length", "corrupt HDR"); } - if (scanline == NULL) scanline = (stbi_uc *) malloc(width * 4); - - for (k = 0; k < 4; ++k) { - i = 0; - while (i < width) { - count = get8(s); - if (count > 128) { - // Run - value = get8(s); - count -= 128; - for (z = 0; z < count; ++z) - scanline[i++ * 4 + k] = value; - } else { - // Dump - for (z = 0; z < count; ++z) - scanline[i++ * 4 + k] = get8(s); - } - } - } - for (i=0; i < width; ++i) - hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); - } - free(scanline); - } - - return hdr_data; -} - -static stbi_uc *hdr_load_rgbe(stbi *s, int *x, int *y, int *comp, int req_comp) -{ - char buffer[HDR_BUFLEN]; - char *token; - int valid = 0; - int width, height; - stbi_uc *scanline; - stbi_uc *rgbe_data; - int len; - unsigned char count, value; - int i, j, k, c1,c2, z; - - - // Check identifier - if (strcmp(hdr_gettoken(s,buffer), "#?RADIANCE") != 0) - return epuc("not HDR", "Corrupt HDR image"); - - // Parse header - while(1) { - token = hdr_gettoken(s,buffer); - if (token[0] == 0) break; - if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; - } - - if (!valid) return epuc("unsupported format", "Unsupported HDR format"); - - // Parse width and height - // can't use sscanf() if we're not using stdio! - token = hdr_gettoken(s,buffer); - if (strncmp(token, "-Y ", 3)) return epuc("unsupported data layout", "Unsupported HDR format"); - token += 3; - height = strtol(token, &token, 10); - while (*token == ' ') ++token; - if (strncmp(token, "+X ", 3)) return epuc("unsupported data layout", "Unsupported HDR format"); - token += 3; - width = strtol(token, NULL, 10); - - *x = width; - *y = height; - - // RGBE _MUST_ come out as 4 components - *comp = 4; - req_comp = 4; - - // Read data - rgbe_data = (stbi_uc *) malloc(height * width * req_comp * sizeof(stbi_uc)); - // point to the beginning - scanline = rgbe_data; - - // Load image data - // image data is stored as some number of scan lines - if( width < 8 || width >= 32768) { - // Read flat data - for (j=0; j < height; ++j) { - for (i=0; i < width; ++i) { - main_decode_loop: - //getn(rgbe, 4); - getn(s,scanline, 4); - scanline += 4; - } - } - } else { - // Read RLE-encoded data - for (j = 0; j < height; ++j) { - c1 = get8(s); - c2 = get8(s); - len = get8(s); - if (c1 != 2 || c2 != 2 || (len & 0x80)) { - // not run-length encoded, so we have to actually use THIS data as a decoded - // pixel (note this can't be a valid pixel--one of RGB must be >= 128) - scanline[0] = c1; - scanline[1] = c2; - scanline[2] = len; - scanline[3] = get8(s); - scanline += 4; - i = 1; - j = 0; - goto main_decode_loop; // yes, this is insane; blame the insane format - } - len <<= 8; - len |= get8(s); - if (len != width) { free(rgbe_data); return epuc("invalid decoded scanline length", "corrupt HDR"); } - for (k = 0; k < 4; ++k) { - i = 0; - while (i < width) { - count = get8(s); - if (count > 128) { - // Run - value = get8(s); - count -= 128; - for (z = 0; z < count; ++z) - scanline[i++ * 4 + k] = value; - } else { - // Dump - for (z = 0; z < count; ++z) - scanline[i++ * 4 + k] = get8(s); - } - } - } - // move the scanline on - scanline += 4 * width; - } - } - - return rgbe_data; -} - -#ifndef STBI_NO_STDIO -float *stbi_hdr_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) -{ - stbi s; - start_file(&s,f); - return hdr_load(&s,x,y,comp,req_comp); -} - -stbi_uc *stbi_hdr_load_rgbe_file(FILE *f, int *x, int *y, int *comp, int req_comp) -{ - stbi s; - start_file(&s,f); - return hdr_load_rgbe(&s,x,y,comp,req_comp); -} - -stbi_uc *stbi_hdr_load_rgbe (char const *filename, int *x, int *y, int *comp, int req_comp) -{ - FILE *f = fopen(filename, "rb"); - unsigned char *result; - if (!f) return epuc("can't fopen", "Unable to open file"); - result = stbi_hdr_load_rgbe_file(f,x,y,comp,req_comp); - fclose(f); - return result; -} -#endif - -float *stbi_hdr_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) -{ - stbi s; - start_mem(&s,buffer, len); - return hdr_load(&s,x,y,comp,req_comp); -} - -stbi_uc *stbi_hdr_load_rgbe_memory(stbi_uc *buffer, int len, int *x, int *y, int *comp, int req_comp) -{ - stbi s; - start_mem(&s,buffer, len); - return hdr_load_rgbe(&s,x,y,comp,req_comp); -} - -#endif // STBI_NO_HDR - -/////////////////////// write image /////////////////////// - -#ifndef STBI_NO_WRITE - -static void write8(FILE *f, int x) { uint8 z = (uint8) x; fwrite(&z,1,1,f); } - -static void writefv(FILE *f, char *fmt, va_list v) -{ - while (*fmt) { - switch (*fmt++) { - case ' ': break; - case '1': { uint8 x = va_arg(v, int); write8(f,x); break; } - case '2': { int16 x = va_arg(v, int); write8(f,x); write8(f,x>>8); break; } - case '4': { int32 x = va_arg(v, int); write8(f,x); write8(f,x>>8); write8(f,x>>16); write8(f,x>>24); break; } - default: - assert(0); - va_end(v); - return; - } - } -} - -static void writef(FILE *f, char *fmt, ...) -{ - va_list v; - va_start(v, fmt); - writefv(f,fmt,v); - va_end(v); -} - -static void write_pixels(FILE *f, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad) -{ - uint8 bg[3] = { 255, 0, 255}, px[3]; - uint32 zero = 0; - int i,j,k, j_end; - - if (vdir < 0) - j_end = -1, j = y-1; - else - j_end = y, j = 0; - - for (; j != j_end; j += vdir) { - for (i=0; i < x; ++i) { - uint8 *d = (uint8 *) data + (j*x+i)*comp; - if (write_alpha < 0) - fwrite(&d[comp-1], 1, 1, f); - switch (comp) { - case 1: - case 2: writef(f, "111", d[0],d[0],d[0]); - break; - case 4: - if (!write_alpha) { - for (k=0; k < 3; ++k) - px[k] = bg[k] + ((d[k] - bg[k]) * d[3])/255; - writef(f, "111", px[1-rgb_dir],px[1],px[1+rgb_dir]); - break; - } - /* FALLTHROUGH */ - case 3: - writef(f, "111", d[1-rgb_dir],d[1],d[1+rgb_dir]); - break; - } - if (write_alpha > 0) - fwrite(&d[comp-1], 1, 1, f); - } - fwrite(&zero,scanline_pad,1,f); - } -} - -static int outfile(char const *filename, int rgb_dir, int vdir, int x, int y, int comp, void *data, int alpha, int pad, char *fmt, ...) -{ - FILE *f = fopen(filename, "wb"); - if (f) { - va_list v; - va_start(v, fmt); - writefv(f, fmt, v); - va_end(v); - write_pixels(f,rgb_dir,vdir,x,y,comp,data,alpha,pad); - fclose(f); - } - return f != NULL; -} - -int stbi_write_bmp(char const *filename, int x, int y, int comp, void *data) -{ - int pad = (-x*3) & 3; - return outfile(filename,-1,-1,x,y,comp,data,0,pad, - "11 4 22 4" "4 44 22 444444", - 'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header - 40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header -} - -int stbi_write_tga(char const *filename, int x, int y, int comp, void *data) -{ - int has_alpha = !(comp & 1); - return outfile(filename, -1,-1, x, y, comp, data, has_alpha, 0, - "111 221 2222 11", 0,0,2, 0,0,0, 0,0,x,y, 24+8*has_alpha, 8*has_alpha); -} - -// any other image formats that do interleaved rgb data? -// PNG: requires adler32,crc32 -- significant amount of code -// PSD: no, channels output separately -// TIFF: no, stripwise-interleaved... i think - -#endif // STBI_NO_WRITE - -// add in my DDS loading support -#ifndef STBI_NO_DDS -#include "stbi_DDS_aug_c.h" -#endif - -#ifdef _MSC_VER -#pragma warning(pop) -#else -#pragma GCC diagnostic pop -#endif diff --git a/lib/soil/stb_image_aug.h b/lib/soil/stb_image_aug.h deleted file mode 100644 index e59f2eb83..000000000 --- a/lib/soil/stb_image_aug.h +++ /dev/null @@ -1,354 +0,0 @@ -/* stbi-1.16 - public domain JPEG/PNG reader - http://nothings.org/stb_image.c - when you control the images you're loading - - QUICK NOTES: - Primarily of interest to game developers and other people who can - avoid problematic images and only need the trivial interface - - JPEG baseline (no JPEG progressive, no oddball channel decimations) - PNG non-interlaced - BMP non-1bpp, non-RLE - TGA (not sure what subset, if a subset) - PSD (composited view only, no extra channels) - HDR (radiance rgbE format) - writes BMP,TGA (define STBI_NO_WRITE to remove code) - decoded from memory or through stdio FILE (define STBI_NO_STDIO to remove code) - supports installable dequantizing-IDCT, YCbCr-to-RGB conversion (define STBI_SIMD) - - TODO: - stbi_info_* - - history: - 1.16 major bugfix - convert_format converted one too many pixels - 1.15 initialize some fields for thread safety - 1.14 fix threadsafe conversion bug; header-file-only version (#define STBI_HEADER_FILE_ONLY before including) - 1.13 threadsafe - 1.12 const qualifiers in the API - 1.11 Support installable IDCT, colorspace conversion routines - 1.10 Fixes for 64-bit (don't use "unsigned long") - optimized upsampling by Fabian "ryg" Giesen - 1.09 Fix format-conversion for PSD code (bad global variables!) - 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz - 1.07 attempt to fix C++ warning/errors again - 1.06 attempt to fix C++ warning/errors again - 1.05 fix TGA loading to return correct *comp and use good luminance calc - 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free - 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR - 1.02 support for (subset of) HDR files, float interface for preferred access to them - 1.01 fix bug: possible bug in handling right-side up bmps... not sure - fix bug: the stbi_bmp_load() and stbi_tga_load() functions didn't work at all - 1.00 interface to zlib that skips zlib header - 0.99 correct handling of alpha in palette - 0.98 TGA loader by lonesock; dynamically add loaders (untested) - 0.97 jpeg errors on too large a file; also catch another malloc failure - 0.96 fix detection of invalid v value - particleman@mollyrocket forum - 0.95 during header scan, seek to markers in case of padding - 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same - 0.93 handle jpegtran output; verbose errors - 0.92 read 4,8,16,24,32-bit BMP files of several formats - 0.91 output 24-bit Windows 3.0 BMP files - 0.90 fix a few more warnings; bump version number to approach 1.0 - 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd - 0.60 fix compiling as c++ - 0.59 fix warnings: merge Dave Moore's -Wall fixes - 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian - 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less - than 16 available - 0.56 fix bug: zlib uncompressed mode len vs. nlen - 0.55 fix bug: restart_interval not initialized to 0 - 0.54 allow NULL for 'int *comp' - 0.53 fix bug in png 3->4; speedup png decoding - 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments - 0.51 obey req_comp requests, 1-component jpegs return as 1-component, - on 'test' only check type, not whether we support this variant -*/ - -#ifndef HEADER_STB_IMAGE_AUGMENTED -#define HEADER_STB_IMAGE_AUGMENTED - -//// begin header file //////////////////////////////////////////////////// -// -// Limitations: -// - no progressive/interlaced support (jpeg, png) -// - 8-bit samples only (jpeg, png) -// - not threadsafe -// - channel subsampling of at most 2 in each dimension (jpeg) -// - no delayed line count (jpeg) -- IJG doesn't support either -// -// Basic usage (see HDR discussion below): -// int x,y,n; -// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); -// // ... process data if not NULL ... -// // ... x = width, y = height, n = # 8-bit components per pixel ... -// // ... replace '0' with '1'..'4' to force that many components per pixel -// stbi_image_free(data) -// -// Standard parameters: -// int *x -- outputs image width in pixels -// int *y -- outputs image height in pixels -// int *comp -- outputs # of image components in image file -// int req_comp -- if non-zero, # of image components requested in result -// -// The return value from an image loader is an 'unsigned char *' which points -// to the pixel data. The pixel data consists of *y scanlines of *x pixels, -// with each pixel consisting of N interleaved 8-bit components; the first -// pixel pointed to is top-left-most in the image. There is no padding between -// image scanlines or between pixels, regardless of format. The number of -// components N is 'req_comp' if req_comp is non-zero, or *comp otherwise. -// If req_comp is non-zero, *comp has the number of components that _would_ -// have been output otherwise. E.g. if you set req_comp to 4, you will always -// get RGBA output, but you can check *comp to easily see if it's opaque. -// -// An output image with N components has the following components interleaved -// in this order in each pixel: -// -// N=#comp components -// 1 grey -// 2 grey, alpha -// 3 red, green, blue -// 4 red, green, blue, alpha -// -// If image loading fails for any reason, the return value will be NULL, -// and *x, *y, *comp will be unchanged. The function stbi_failure_reason() -// can be queried for an extremely brief, end-user unfriendly explanation -// of why the load failed. Define STBI_NO_FAILURE_STRINGS to avoid -// compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly -// more user-friendly ones. -// -// Paletted PNG and BMP images are automatically depalettized. -// -// -// =========================================================================== -// -// HDR image support (disable by defining STBI_NO_HDR) -// -// stb_image now supports loading HDR images in general, and currently -// the Radiance .HDR file format, although the support is provided -// generically. You can still load any file through the existing interface; -// if you attempt to load an HDR file, it will be automatically remapped to -// LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; -// both of these constants can be reconfigured through this interface: -// -// stbi_hdr_to_ldr_gamma(2.2f); -// stbi_hdr_to_ldr_scale(1.0f); -// -// (note, do not use _inverse_ constants; stbi_image will invert them -// appropriately). -// -// Additionally, there is a new, parallel interface for loading files as -// (linear) floats to preserve the full dynamic range: -// -// float *data = stbi_loadf(filename, &x, &y, &n, 0); -// -// If you load LDR images through this interface, those images will -// be promoted to floating point values, run through the inverse of -// constants corresponding to the above: -// -// stbi_ldr_to_hdr_scale(1.0f); -// stbi_ldr_to_hdr_gamma(2.2f); -// -// Finally, given a filename (or an open file or memory block--see header -// file for details) containing image data, you can query for the "most -// appropriate" interface to use (that is, whether the image is HDR or -// not), using: -// -// stbi_is_hdr(char *filename); - -#ifndef STBI_NO_STDIO -#include -#endif - -#define STBI_VERSION 1 - -enum -{ - STBI_default = 0, // only used for req_comp - - STBI_grey = 1, - STBI_grey_alpha = 2, - STBI_rgb = 3, - STBI_rgb_alpha = 4, -}; - -typedef unsigned char stbi_uc; - -#ifdef __cplusplus -extern "C" { -#endif - -// WRITING API - -#if !defined(STBI_NO_WRITE) && !defined(STBI_NO_STDIO) -// write a BMP/TGA file given tightly packed 'comp' channels (no padding, nor bmp-stride-padding) -// (you must include the appropriate extension in the filename). -// returns TRUE on success, FALSE if couldn't open file, error writing file -extern int stbi_write_bmp (char const *filename, int x, int y, int comp, void *data); -extern int stbi_write_tga (char const *filename, int x, int y, int comp, void *data); -#endif - -// PRIMARY API - works on images of any type - -// load image by filename, open file, or memory buffer -#ifndef STBI_NO_STDIO -extern stbi_uc *stbi_load (char const *filename, int *x, int *y, int *comp, int req_comp); -extern stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); -extern int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); -#endif -extern stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); -// for stbi_load_from_file, file pointer is left pointing immediately after image - -#ifndef STBI_NO_HDR -#ifndef STBI_NO_STDIO -extern float *stbi_loadf (char const *filename, int *x, int *y, int *comp, int req_comp); -extern float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); -#endif -extern float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); - -extern void stbi_hdr_to_ldr_gamma(float gamma); -extern void stbi_hdr_to_ldr_scale(float scale); - -extern void stbi_ldr_to_hdr_gamma(float gamma); -extern void stbi_ldr_to_hdr_scale(float scale); - -#endif // STBI_NO_HDR - -// get a VERY brief reason for failure -// NOT THREADSAFE -extern char *stbi_failure_reason (void); - -// free the loaded image -- this is just free() -extern void stbi_image_free (void *retval_from_stbi_load); - -// get image dimensions & components without fully decoding -extern int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); -extern int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); -#ifndef STBI_NO_STDIO -extern int stbi_info (char const *filename, int *x, int *y, int *comp); -extern int stbi_is_hdr (char const *filename); -extern int stbi_is_hdr_from_file(FILE *f); -#endif - -// ZLIB client - used by PNG, available for other purposes - -extern char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); -extern char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); -extern int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); - -extern char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); -extern int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); - -// TYPE-SPECIFIC ACCESS - -// is it a jpeg? -extern int stbi_jpeg_test_memory (stbi_uc const *buffer, int len); -extern stbi_uc *stbi_jpeg_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); -extern int stbi_jpeg_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); - -#ifndef STBI_NO_STDIO -extern stbi_uc *stbi_jpeg_load (char const *filename, int *x, int *y, int *comp, int req_comp); -extern int stbi_jpeg_test_file (FILE *f); -extern stbi_uc *stbi_jpeg_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); - -extern int stbi_jpeg_info (char const *filename, int *x, int *y, int *comp); -extern int stbi_jpeg_info_from_file (FILE *f, int *x, int *y, int *comp); -#endif - -// is it a png? -extern int stbi_png_test_memory (stbi_uc const *buffer, int len); -extern stbi_uc *stbi_png_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); -extern int stbi_png_info_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp); - -#ifndef STBI_NO_STDIO -extern stbi_uc *stbi_png_load (char const *filename, int *x, int *y, int *comp, int req_comp); -extern int stbi_png_info (char const *filename, int *x, int *y, int *comp); -extern int stbi_png_test_file (FILE *f); -extern stbi_uc *stbi_png_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); -extern int stbi_png_info_from_file (FILE *f, int *x, int *y, int *comp); -#endif - -// is it a bmp? -extern int stbi_bmp_test_memory (stbi_uc const *buffer, int len); - -extern stbi_uc *stbi_bmp_load (char const *filename, int *x, int *y, int *comp, int req_comp); -extern stbi_uc *stbi_bmp_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); -#ifndef STBI_NO_STDIO -extern int stbi_bmp_test_file (FILE *f); -extern stbi_uc *stbi_bmp_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); -#endif - -// is it a tga? -extern int stbi_tga_test_memory (stbi_uc const *buffer, int len); - -extern stbi_uc *stbi_tga_load (char const *filename, int *x, int *y, int *comp, int req_comp); -extern stbi_uc *stbi_tga_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); -#ifndef STBI_NO_STDIO -extern int stbi_tga_test_file (FILE *f); -extern stbi_uc *stbi_tga_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); -#endif - -// is it a psd? -extern int stbi_psd_test_memory (stbi_uc const *buffer, int len); - -extern stbi_uc *stbi_psd_load (char const *filename, int *x, int *y, int *comp, int req_comp); -extern stbi_uc *stbi_psd_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); -#ifndef STBI_NO_STDIO -extern int stbi_psd_test_file (FILE *f); -extern stbi_uc *stbi_psd_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); -#endif - -// is it an hdr? -extern int stbi_hdr_test_memory (stbi_uc const *buffer, int len); - -extern float * stbi_hdr_load (char const *filename, int *x, int *y, int *comp, int req_comp); -extern float * stbi_hdr_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); -extern stbi_uc *stbi_hdr_load_rgbe (char const *filename, int *x, int *y, int *comp, int req_comp); -extern float * stbi_hdr_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); -#ifndef STBI_NO_STDIO -extern int stbi_hdr_test_file (FILE *f); -extern float * stbi_hdr_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); -extern stbi_uc *stbi_hdr_load_rgbe_file (FILE *f, int *x, int *y, int *comp, int req_comp); -#endif - -// define new loaders -typedef struct -{ - int (*test_memory)(stbi_uc const *buffer, int len); - stbi_uc * (*load_from_memory)(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); - #ifndef STBI_NO_STDIO - int (*test_file)(FILE *f); - stbi_uc * (*load_from_file)(FILE *f, int *x, int *y, int *comp, int req_comp); - #endif -} stbi_loader; - -// register a loader by filling out the above structure (you must defined ALL functions) -// returns 1 if added or already added, 0 if not added (too many loaders) -// NOT THREADSAFE -extern int stbi_register_loader(stbi_loader *loader); - -// define faster low-level operations (typically SIMD support) -#if STBI_SIMD -typedef void (*stbi_idct_8x8)(uint8 *out, int out_stride, short data[64], unsigned short *dequantize); -// compute an integer IDCT on "input" -// input[x] = data[x] * dequantize[x] -// write results to 'out': 64 samples, each run of 8 spaced by 'out_stride' -// CLAMP results to 0..255 -typedef void (*stbi_YCbCr_to_RGB_run)(uint8 *output, uint8 const *y, uint8 const *cb, uint8 const *cr, int count, int step); -// compute a conversion from YCbCr to RGB -// 'count' pixels -// write pixels to 'output'; each pixel is 'step' bytes (either 3 or 4; if 4, write '255' as 4th), order R,G,B -// y: Y input channel -// cb: Cb input channel; scale/biased to be 0..255 -// cr: Cr input channel; scale/biased to be 0..255 - -extern void stbi_install_idct(stbi_idct_8x8 func); -extern void stbi_install_YCbCr_to_RGB(stbi_YCbCr_to_RGB_run func); -#endif // STBI_SIMD - -#ifdef __cplusplus -} -#endif - -// -// -//// end header file ///////////////////////////////////////////////////// -#endif // STBI_INCLUDE_STB_IMAGE_H diff --git a/lib/soil/stbi_DDS_aug.h b/lib/soil/stbi_DDS_aug.h deleted file mode 100644 index c7da9f789..000000000 --- a/lib/soil/stbi_DDS_aug.h +++ /dev/null @@ -1,21 +0,0 @@ -/* - adding DDS loading support to stbi -*/ - -#ifndef HEADER_STB_IMAGE_DDS_AUGMENTATION -#define HEADER_STB_IMAGE_DDS_AUGMENTATION - -// is it a DDS file? -extern int stbi_dds_test_memory (stbi_uc const *buffer, int len); - -extern stbi_uc *stbi_dds_load (char *filename, int *x, int *y, int *comp, int req_comp); -extern stbi_uc *stbi_dds_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); -#ifndef STBI_NO_STDIO -extern int stbi_dds_test_file (FILE *f); -extern stbi_uc *stbi_dds_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); -#endif - -// -// -//// end header file ///////////////////////////////////////////////////// -#endif // HEADER_STB_IMAGE_DDS_AUGMENTATION diff --git a/res/shaders/fo4_default.frag b/res/shaders/fo4_default.frag index 3463ab8f8..bcc9724fe 100644 --- a/res/shaders/fo4_default.frag +++ b/res/shaders/fo4_default.frag @@ -254,8 +254,8 @@ void main( void ) float specMask = 1.0; vec3 spec = vec3(0.0); if ( hasSpecularMap ) { - g = specMap.r; - s = specMap.g; + g = specMap.g; + s = specMap.r; smoothness = g * specGlossiness; roughness = 1.0 - smoothness; float fSpecularPower = exp2( smoothness * 10 + 1 ); diff --git a/res/shaders/fo4_default.vert b/res/shaders/fo4_default.vert index 62e989684..ed72d8219 100644 --- a/res/shaders/fo4_default.vert +++ b/res/shaders/fo4_default.vert @@ -22,10 +22,9 @@ void main( void ) t = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz); b = normalize(gl_NormalMatrix * gl_MultiTexCoord2.xyz); - // NOTE: b<->t - mat3 tbnMatrix = mat3(t.x, b.x, N.x, - t.y, b.y, N.y, - t.z, b.z, N.z); + mat3 tbnMatrix = mat3(b.x, t.x, N.x, + b.y, t.y, N.y, + b.z, t.z, N.z); v = vec3(gl_ModelViewMatrix * gl_Vertex); diff --git a/res/shaders/fo4_effectshader.frag b/res/shaders/fo4_effectshader.frag index e432426bf..07d3fd72a 100644 --- a/res/shaders/fo4_effectshader.frag +++ b/res/shaders/fo4_effectshader.frag @@ -80,7 +80,7 @@ void main( void ) float NdotNegL = max( dot(normal, -L), 0.000001 ); vec3 reflected = reflect( V, normal ); - vec3 reflectedVS = t * reflected.x + b * reflected.y + N * reflected.z; + vec3 reflectedVS = b * reflected.x + t * reflected.y + N * reflected.z; vec3 reflectedWS = vec3( worldMatrix * (gl_ModelViewMatrixInverse * vec4( reflectedVS, 0.0 )) ); if ( greyscaleAlpha ) diff --git a/res/shaders/fo4_effectshader.vert b/res/shaders/fo4_effectshader.vert index 5776e7dae..909d462fa 100644 --- a/res/shaders/fo4_effectshader.vert +++ b/res/shaders/fo4_effectshader.vert @@ -21,10 +21,9 @@ void main( void ) t = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz); b = normalize(gl_NormalMatrix * gl_MultiTexCoord2.xyz); - // NOTE: b<->t - mat3 tbnMatrix = mat3(t.x, b.x, N.x, - t.y, b.y, N.y, - t.z, b.z, N.z); + mat3 tbnMatrix = mat3(b.x, t.x, N.x, + b.y, t.y, N.y, + b.z, t.z, N.z); v = vec3(gl_ModelViewMatrix * gl_Vertex); diff --git a/res/shaders/fo4_env.frag b/res/shaders/fo4_env.frag index 09cadb9b6..dd1e99a2d 100644 --- a/res/shaders/fo4_env.frag +++ b/res/shaders/fo4_env.frag @@ -232,7 +232,7 @@ void main( void ) float NdotNegL = max( dot(normal, -L), FLT_EPSILON ); vec3 reflected = reflect( V, normal ); - vec3 reflectedVS = t * reflected.x + b * reflected.y + N * reflected.z; + vec3 reflectedVS = b * reflected.x + t * reflected.y + N * reflected.z; vec3 reflectedWS = vec3( worldMatrix * (gl_ModelViewMatrixInverse * vec4( reflectedVS, 0.0 )) ); @@ -259,8 +259,8 @@ void main( void ) float specMask = 1.0; vec3 spec = vec3(0.0); if ( hasSpecularMap ) { - g = specMap.r; - s = specMap.g; + g = specMap.g; + s = specMap.r; smoothness = g * specGlossiness; roughness = 1.0 - smoothness; float fSpecularPower = exp2( smoothness * 10 + 1 ); diff --git a/res/shaders/fo4_env.vert b/res/shaders/fo4_env.vert index 62e989684..ed72d8219 100644 --- a/res/shaders/fo4_env.vert +++ b/res/shaders/fo4_env.vert @@ -22,10 +22,9 @@ void main( void ) t = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz); b = normalize(gl_NormalMatrix * gl_MultiTexCoord2.xyz); - // NOTE: b<->t - mat3 tbnMatrix = mat3(t.x, b.x, N.x, - t.y, b.y, N.y, - t.z, b.z, N.z); + mat3 tbnMatrix = mat3(b.x, t.x, N.x, + b.y, t.y, N.y, + b.z, t.z, N.z); v = vec3(gl_ModelViewMatrix * gl_Vertex); diff --git a/src/gl/gltexloaders.cpp b/src/gl/gltexloaders.cpp index 2d532ad8a..fd4b3d1ab 100644 --- a/src/gl/gltexloaders.cpp +++ b/src/gl/gltexloaders.cpp @@ -37,7 +37,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "gl/dds/dds_api.h" #include "gl/dds/DirectDrawSurface.h" // unused? check if upstream has cleaner or documented API yet -#include +#include #include #include @@ -618,8 +618,8 @@ GLuint texLoadDXT( QIODevice & f, GLenum glFormat, int /*blockSize*/, quint32 /* if ( glFormat == GL_COMPRESSED_LUMINANCE_ALPHA_3DC_ATI ) { for ( quint32 y = 0; y < h; y++ ) { for ( quint32 x = 0; x < w; x++ ) { - *dst++ = src->g; *dst++ = src->r; + *dst++ = src->g; *dst++ = 255 - src->b; *dst++ = 255; src++; From 6410955d3f7886499d833fac620f1af702cdaa38 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Thu, 12 Oct 2017 11:57:27 -0400 Subject: [PATCH 078/152] [GL] Add GLI/GLM --- .gitmodules | 3 +++ NifSkope.pro | 10 +++++++++- lib/gli | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) create mode 160000 lib/gli diff --git a/.gitmodules b/.gitmodules index f39f05e05..219658bb9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "lib/zlib"] path = lib/zlib url = https://github.com/madler/zlib.git +[submodule "lib/gli"] + path = lib/gli + url = https://github.com/g-truc/gli.git diff --git a/NifSkope.pro b/NifSkope.pro index 72983b4ac..e225c4ceb 100644 --- a/NifSkope.pro +++ b/NifSkope.pro @@ -17,7 +17,7 @@ contains(QT_VERSION, ^5\\.[0-6]\\..*) { CONFIG += c++14 # Dependencies -CONFIG += nvtristrip qhull soil2 zlib lz4 fsengine +CONFIG += nvtristrip qhull soil2 zlib lz4 fsengine gli # Debug/Release options CONFIG(debug, debug|release) { @@ -386,6 +386,14 @@ soil2 { lib/SOIL2/SOIL2.c } +gli { + INCLUDEPATH += lib/gli/gli lib/gli/external + HEADERS += $$files($$PWD/lib/gli/gli/*.hpp, true) + HEADERS += $$files($$PWD/lib/gli/gli/*.inl, true) + HEADERS += $$files($$PWD/lib/gli/external/glm/*.hpp, true) + HEADERS += $$files($$PWD/lib/gli/external/glm/*.inl, true) +} + zlib { INCLUDEPATH += lib/zlib diff --git a/lib/gli b/lib/gli new file mode 160000 index 000000000..8e43030b3 --- /dev/null +++ b/lib/gli @@ -0,0 +1 @@ +Subproject commit 8e43030b3e12bb58a4663d85adc5c752f89099c0 From 35d5c442326ca501044b60cd2dff39074ef57e26 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sat, 14 Oct 2017 17:35:13 -0400 Subject: [PATCH 079/152] [GL] Fix palettized NiPixelData texture loading ByteColor4 was internalized as a type so r/g/b/a index accessors were no longer valid. --- src/gl/gltexloaders.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/gl/gltexloaders.cpp b/src/gl/gltexloaders.cpp index fd4b3d1ab..9901c7258 100644 --- a/src/gl/gltexloaders.cpp +++ b/src/gl/gltexloaders.cpp @@ -1133,11 +1133,11 @@ bool texLoad( const QModelIndex & iData, QString & texformat, GLuint & width, GL if ( nmap > 0 && iPaletteArray.isValid() ) { for ( uint i = 0; i < nmap; ++i ) { - QModelIndex iRGBElem = iPaletteArray.child( i, 0 ); - quint8 r = nif->get( iRGBElem, "r" ); - quint8 g = nif->get( iRGBElem, "g" ); - quint8 b = nif->get( iRGBElem, "b" ); - quint8 a = nif->get( iRGBElem, "a" ); + auto color = nif->get( iPaletteArray.child( i, 0 ) ).toQColor(); + quint8 r = color.red(); + quint8 g = color.green(); + quint8 b = color.blue(); + quint8 a = color.alpha(); map[i] = ( (quint32)( ( r | ( (quint16)g << 8 ) ) | ( ( (quint32)b ) << 16 ) | ( ( (quint32)a ) << 24 ) ) ); } } From e8b7127591f8a2ed68376c906bc51e1ac913e2cf Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sun, 15 Oct 2017 19:39:43 -0400 Subject: [PATCH 080/152] nifxml 0.9 sync, internal BSVertexDesc type Add internal type to manage correct editing and writing of BSTriShape's vertex desc. Added UI to manage the attributes with the other bytes hidden from the users (VF1-VF5). The first 5 bytes are sizes/offsets generated from what vertex attributes the geometry has. Also general syncing with naming changes in nifxml 0.9. --- res/shaders/fo4_default.prog | 2 +- res/shaders/fo4_env.prog | 2 +- res/shaders/sk_default.prog | 2 +- res/shaders/sk_env.prog | 2 +- res/shaders/sk_glowmap.prog | 2 +- res/shaders/sk_msn.prog | 2 +- res/shaders/sk_multilayer.prog | 2 +- res/shaders/sk_parallax.prog | 2 +- src/data/niftypes.h | 249 +++++++++++++++++++++++++++++++++ src/data/nifvalue.cpp | 22 +++ src/data/nifvalue.h | 11 ++ src/gl/bsshape.cpp | 5 +- src/gl/glmesh.cpp | 8 +- src/gl/glnode.cpp | 20 ++- src/gl/gltexloaders.cpp | 14 +- src/gl/renderer.cpp | 2 + src/io/nifstream.cpp | 16 ++- src/model/basemodel.cpp | 4 +- src/spells/blocks.cpp | 9 +- src/spells/flags.cpp | 102 ++++++++++++-- src/spells/normals.cpp | 12 +- src/spells/tangentspace.cpp | 4 +- src/spells/texture.cpp | 4 +- src/ui/widgets/uvedit.cpp | 24 ++-- 24 files changed, 456 insertions(+), 66 deletions(-) diff --git a/res/shaders/fo4_default.prog b/res/shaders/fo4_default.prog index 7680325f0..8e7b44bd4 100644 --- a/res/shaders/fo4_default.prog +++ b/res/shaders/fo4_default.prog @@ -13,7 +13,7 @@ checkgroup begin and check BSLightingShaderProperty check BSLightingShaderProperty/Skyrim Shader Type != 1 check BSLightingShaderProperty/Skyrim Shader Type != 16 - check NiAVObject/Vertex Size >= 3 + #check NiAVObject/Vertex Size >= 3 checkgroup begin or check BSTriShape check BSSubIndexTriShape diff --git a/res/shaders/fo4_env.prog b/res/shaders/fo4_env.prog index b440605c3..de2ffc6dc 100644 --- a/res/shaders/fo4_env.prog +++ b/res/shaders/fo4_env.prog @@ -15,7 +15,7 @@ checkgroup begin and check BSLightingShaderProperty/Skyrim Shader Type == 1 check BSLightingShaderProperty/Skyrim Shader Type == 16 checkgroup end - check NiAVObject/Vertex Size >= 3 + #check NiAVObject/Vertex Size >= 3 checkgroup begin or check BSTriShape check BSSubIndexTriShape diff --git a/res/shaders/sk_default.prog b/res/shaders/sk_default.prog index 9b064ad51..96cc8066e 100644 --- a/res/shaders/sk_default.prog +++ b/res/shaders/sk_default.prog @@ -20,7 +20,7 @@ checkgroup begin or #check NiTriBasedGeomData/Has Vertex Colors == 1 checkgroup begin or check NiTriBasedGeomData/Has Normals == 1 - check BSTriShape/VF & 128 + check BSTriShape/Vertex Desc & 8 checkgroup end checkgroup end checkgroup end diff --git a/res/shaders/sk_env.prog b/res/shaders/sk_env.prog index f1cd419d4..f81123756 100644 --- a/res/shaders/sk_env.prog +++ b/res/shaders/sk_env.prog @@ -14,7 +14,7 @@ checkgroup begin or check BSLightingShaderProperty checkgroup begin or check NiTriBasedGeomData/Has Normals == 1 - check BSTriShape/VF & 128 + check BSTriShape/Vertex Desc & 8 checkgroup end checkgroup begin or check BSLightingShaderProperty/Skyrim Shader Type == 1 diff --git a/res/shaders/sk_glowmap.prog b/res/shaders/sk_glowmap.prog index aed124697..4011d5c32 100644 --- a/res/shaders/sk_glowmap.prog +++ b/res/shaders/sk_glowmap.prog @@ -15,7 +15,7 @@ checkgroup begin or check BSLightingShaderProperty/Skyrim Shader Type == 2 checkgroup begin or check NiTriBasedGeomData/Has Normals == 1 - check BSTriShape/VF & 128 + check BSTriShape/Vertex Desc & 8 checkgroup end checkgroup end checkgroup end diff --git a/res/shaders/sk_msn.prog b/res/shaders/sk_msn.prog index 66560dc3f..c2423bcf5 100644 --- a/res/shaders/sk_msn.prog +++ b/res/shaders/sk_msn.prog @@ -14,7 +14,7 @@ checkgroup begin or check BSLightingShaderProperty checkgroup begin or check NiTriBasedGeomData/Has Normals == 0 - check BSTriShape/VF !& 128 + check not BSTriShape/Vertex Desc & 8 checkgroup end checkgroup end checkgroup end diff --git a/res/shaders/sk_multilayer.prog b/res/shaders/sk_multilayer.prog index 336e105d7..ec5d4b938 100644 --- a/res/shaders/sk_multilayer.prog +++ b/res/shaders/sk_multilayer.prog @@ -15,7 +15,7 @@ checkgroup begin or check BSLightingShaderProperty/Skyrim Shader Type == 11 checkgroup begin or check NiTriBasedGeomData/Has Normals == 1 - check BSTriShape/VF & 128 + check BSTriShape/Vertex Desc & 8 checkgroup end checkgroup end checkgroup end diff --git a/res/shaders/sk_parallax.prog b/res/shaders/sk_parallax.prog index b6fcb9a8c..718a3af87 100644 --- a/res/shaders/sk_parallax.prog +++ b/res/shaders/sk_parallax.prog @@ -15,7 +15,7 @@ checkgroup begin or check BSLightingShaderProperty/Skyrim Shader Type == 3 checkgroup begin or check NiTriBasedGeomData/Has Normals == 1 - check BSTriShape/VF & 128 + check BSTriShape/Vertex Desc & 8 checkgroup end checkgroup end checkgroup end diff --git a/src/data/niftypes.h b/src/data/niftypes.h index 504ac1e20..0fa55a138 100644 --- a/src/data/niftypes.h +++ b/src/data/niftypes.h @@ -1629,4 +1629,253 @@ class FixedMatrix typedef FixedMatrix ByteMatrix; + +// BS Vertex Desc + +enum VertexAttribute : uchar +{ + VA_POSITION = 0x0, + VA_TEXCOORD0 = 0x1, + VA_TEXCOORD1 = 0x2, + VA_NORMAL = 0x3, + VA_BINORMAL = 0x4, + VA_COLOR = 0x5, + VA_SKINNING = 0x6, + VA_LANDDATA = 0x7, + VA_EYEDATA = 0x8, + VA_COUNT = 9 +}; + +enum VertexFlags : ushort +{ + VF_VERTEX = 1 << VA_POSITION, + VF_UV = 1 << VA_TEXCOORD0, + VF_UV_2 = 1 << VA_TEXCOORD1, + VF_NORMAL = 1 << VA_NORMAL, + VF_TANGENT = 1 << VA_BINORMAL, + VF_COLORS = 1 << VA_COLOR, + VF_SKINNED = 1 << VA_SKINNING, + VF_LANDDATA = 1 << VA_LANDDATA, + VF_EYEDATA = 1 << VA_EYEDATA, + VF_FULLPREC = 0x400 +}; + +const uint64_t DESC_MASK_VERT = 0xFFFFFFFFFFFFFFF0; +const uint64_t DESC_MASK_UVS = 0xFFFFFFFFFFFFFF0F; +const uint64_t DESC_MASK_NBT = 0xFFFFFFFFFFFFF0FF; +const uint64_t DESC_MASK_SKCOL = 0xFFFFFFFFFFFF0FFF; +const uint64_t DESC_MASK_DATA = 0xFFFFFFFFFFF0FFFF; +const uint64_t DESC_MASK_OFFSET = 0xFFFFFF0000000000; +const uint64_t DESC_MASK_FLAGS = ~(DESC_MASK_OFFSET); + +class BSVertexDesc +{ + +public: + BSVertexDesc() + { + } + + // Sets a specific flag + void SetFlag( VertexFlags flag ) + { + desc |= ((uint64_t)flag << 44); + } + + // Removes a specific flag + void RemoveFlag( VertexFlags flag ) + { + desc &= ~((uint64_t)flag << 44); + } + + // Checks for a specific flag + bool HasFlag( VertexFlags flag ) const + { + return ((desc >> 44) & flag) != 0; + } + + // Sets a specific attribute in the flags + void SetFlag( VertexAttribute flag ) + { + desc |= (1ull << (uint64_t)flag << 44); + } + + // Removes a specific attribute in the flags + void RemoveFlag( VertexAttribute flag ) + { + desc &= ~(1ull << (uint64_t)flag << 44); + } + + // Checks for a specific attribute in the flags + bool HasFlag( VertexAttribute flag ) const + { + return ((desc >> 44) & (1ull << (uint64_t)flag)) != 0; + } + + // Sets the vertex size, from bytes + void SetSize( uint size ) + { + desc &= DESC_MASK_VERT; + desc |= (uint64_t)size >> 2; + } + + // Gets the vertex size in bytes + uint GetVertexSize() const + { + return (desc & 0xF) * 4; + } + + // Sets the dynamic vertex size + void MakeDynamic() + { + desc &= DESC_MASK_UVS; + desc |= 0x40; + } + + // Return offset to a specific vertex attribute in the description + uint GetAttributeOffset( VertexAttribute attr ) const + { + return (desc >> (4 * (uchar)attr + 2)) & 0x3C; + } + + // Set offset to a specific vertex attribute in the description + void SetAttributeOffset( VertexAttribute attr, uint offset ) + { + if ( attr != VA_POSITION ) { + desc = ((uint64_t)offset << (4 * (uchar)attr + 2)) | desc & ~(15 << (4 * (uchar)attr + 4)); + } + } + + // Reset the offsets based on the current vertex flags + void ResetAttributeOffsets( uint stream ) + { + uint64_t vertexSize = 0; + + VertexFlags vf = GetFlags(); + ClearAttributeOffsets(); + + uint attributeSizes[VA_COUNT] = {}; + if ( vf & VF_VERTEX ) { + if ( vf & VF_FULLPREC || stream == 100 ) + attributeSizes[VA_POSITION] = 4; + else + attributeSizes[VA_POSITION] = 2; + } + + if ( vf & VF_UV ) + attributeSizes[VA_TEXCOORD0] = 1; + + if ( vf & VF_UV_2 ) + attributeSizes[VA_TEXCOORD1] = 1; + + if ( vf & VF_NORMAL ) { + attributeSizes[VA_NORMAL] = 1; + + if ( vf & VF_TANGENT ) + attributeSizes[VA_BINORMAL] = 1; + } + + if ( vf & VF_COLORS ) + attributeSizes[VA_COLOR] = 1; + + if ( vf & VF_SKINNED ) + attributeSizes[VA_SKINNING] = 3; + + if ( vf & VF_EYEDATA ) + attributeSizes[VA_EYEDATA] = 1; + + for ( int va = 0; va < VA_COUNT; va++ ) { + if ( attributeSizes[va] != 0 ) { + SetAttributeOffset( VertexAttribute(va), vertexSize ); + vertexSize += attributeSizes[va] * 4; + } + } + + SetSize( vertexSize ); + + // SetFlags must be called again because SetAttributeOffset + // appears to clear part of the byte ahead of it + SetFlags( vf ); + } + + void ClearAttributeOffsets() + { + desc &= DESC_MASK_OFFSET; + } + + VertexFlags GetFlags() const + { + return VertexFlags( (desc & DESC_MASK_OFFSET) >> 44 ); + } + + void SetFlags( VertexFlags flags ) + { + desc |= ((uint64_t)flags << 44) | (desc & DESC_MASK_FLAGS); + } + + bool operator==( const BSVertexDesc & v ) const + { + return desc == v.desc; + } + + friend int operator&( const BSVertexDesc& lhs, const int& rhs ) + { + return ((lhs.desc & DESC_MASK_OFFSET) >> 44) & rhs; + } + + friend int operator&( const int& lhs, const BSVertexDesc& rhs ) + { + return ((rhs.desc & DESC_MASK_OFFSET) >> 44) & lhs; + } + + QString toString() const + { + QStringList opts; + + if ( GetFlags() & VF_VERTEX ) + opts << "Vertex"; + if ( GetFlags() & VF_UV ) + opts << "UVs"; + if ( GetFlags() & VF_UV_2 ) + opts << "UVs 2"; + if ( GetFlags() & VF_NORMAL ) + opts << "Normals"; + if ( GetFlags() & VF_TANGENT ) + opts << "Tangents"; + if ( GetFlags() & VF_COLORS ) + opts << "Colors"; + if ( GetFlags() & VF_SKINNED ) + opts << "Skinned"; + if ( GetFlags() & VF_EYEDATA ) + opts << "Eye Data"; + if ( GetFlags() & VF_FULLPREC ) + opts << "Full Prec"; + + return opts.join( " | " ); + } + + friend class NifIStream; + friend class NifOStream; + + friend QDataStream & operator<<( QDataStream & ds, BSVertexDesc & d ); + friend QDataStream & operator>>( QDataStream & ds, BSVertexDesc & d ); + +private: + quint64 desc = 0; + +}; + + +inline QDataStream & operator<<( QDataStream & ds, BSVertexDesc & d ) +{ + ds << d.desc; + return ds; +} + +inline QDataStream & operator>>( QDataStream & ds, BSVertexDesc & d ) +{ + ds >> d.desc; + return ds; +} + #endif diff --git a/src/data/nifvalue.cpp b/src/data/nifvalue.cpp index a72ca3994..c0679f7e8 100644 --- a/src/data/nifvalue.cpp +++ b/src/data/nifvalue.cpp @@ -117,6 +117,7 @@ void NifValue::initialize() typeMap.insert( "HalfVector2", NifValue::tHalfVector2 ); typeMap.insert( "HalfTexCoord", NifValue::tHalfVector2 ); typeMap.insert( "ByteColor4", NifValue::tByteColor4 ); + typeMap.insert( "BSVertexDesc", NifValue::tBSVertexDesc ); enumMap.clear(); } @@ -386,6 +387,9 @@ void NifValue::clear() case tByteColor4: delete static_cast( val.data ); break; + case tBSVertexDesc: + delete static_cast(val.data); + break; case tBlob: delete static_cast( val.data ); break; @@ -462,6 +466,9 @@ void NifValue::changeType( Type t ) case tStringIndex: val.u32 = 0xffffffff; return; + case tBSVertexDesc: + val.data = new BSVertexDesc(); + return; case tBlob: val.data = new QByteArray(); return; @@ -528,6 +535,9 @@ void NifValue::operator=( const NifValue & other ) case tBlob: *static_cast( val.data ) = *static_cast( other.val.data ); return; + case tBSVertexDesc: + *static_cast(val.data) = *static_cast(other.val.data); + return; default: val = other.val; return; @@ -696,6 +706,16 @@ bool NifValue::operator==( const NifValue & other ) const return *m1 == *m2; } + case tBSVertexDesc: + { + auto d1 = static_cast(val.data); + auto d2 = static_cast(other.val.data); + + if ( !d1 || !d2 ) + return false; + + return *d1 == *d2; + } case tNone: default: return false; @@ -989,6 +1009,8 @@ QString NifValue::toString() const { return *static_cast( val.data ); } + case tBSVertexDesc: + return static_cast(val.data)->toString(); case tBlob: { QByteArray * array = static_cast( val.data ); diff --git a/src/data/nifvalue.h b/src/data/nifvalue.h index 6408f883a..15428bdc7 100644 --- a/src/data/nifvalue.h +++ b/src/data/nifvalue.h @@ -125,6 +125,7 @@ class NifValue tByteVector3 = 40, tHalfVector2 = 41, tByteColor4 = 42, + tBSVertexDesc = 43, tNone = 0xff }; @@ -618,6 +619,10 @@ template <> inline ByteMatrix * NifValue::get() const return nullptr; } +template <> inline BSVertexDesc NifValue::get() const +{ + return getType( tBSVertexDesc ); +} //! Set the data from a boolean. Return true if successful. template <> inline bool NifValue::set( const bool & b ) @@ -748,6 +753,12 @@ template <> inline bool NifValue::set( const Quat & x ) return false; } +//! Set the data from a BSVertexDesc. Return true if successful. +template <> inline bool NifValue::set( const BSVertexDesc & x ) +{ + return setType( tBSVertexDesc, x ); +} + //! Check whether the data is a boolean. template <> inline bool NifValue::ask( bool * ) const diff --git a/src/gl/bsshape.cpp b/src/gl/bsshape.cpp index 9d9714801..7197b6dc0 100644 --- a/src/gl/bsshape.cpp +++ b/src/gl/bsshape.cpp @@ -44,11 +44,12 @@ void BSShape::update( const NifModel * nif, const QModelIndex & index ) nifVersion = nif->getUserVersion2(); - auto vertexFlags = nif->get( iBlock, "VF" ); + auto vertexFlags = nif->get( iBlock, "Vertex Desc" ); + bool isDynamic = nif->inherits( iBlock, "BSDynamicTriShape" ); bool isDataOnSkin = false; - bool isSkinned = vertexFlags & 0x400; + bool isSkinned = vertexFlags & VertexFlags::VF_SKINNED; if ( nifVersion == 130 ) { skinInstName = "BSSkin::Instance"; skinDataName = "BSSkin::BoneData"; diff --git a/src/gl/glmesh.cpp b/src/gl/glmesh.cpp index 8acca7902..2b8a37faf 100644 --- a/src/gl/glmesh.cpp +++ b/src/gl/glmesh.cpp @@ -109,12 +109,12 @@ void Shape::setController( const NifModel * nif, const QModelIndex & iController void Shape::updateShaderProperties( const NifModel * nif ) { - QVector props = nif->getLinkArray( iBlock, "BS Properties" ); - if ( !props.count() ) + auto prop = nif->getLink( iBlock, "Shader Property" ); + if ( prop == -1 ) return; - QModelIndex iLSP = nif->getBlock( props[0], "BSLightingShaderProperty" ); - QModelIndex iESP = nif->getBlock( props[0], "BSEffectShaderProperty" ); + QModelIndex iLSP = nif->getBlock( prop, "BSLightingShaderProperty" ); + QModelIndex iESP = nif->getBlock( prop, "BSEffectShaderProperty" ); if ( iLSP.isValid() ) { if ( !bslsp ) diff --git a/src/gl/glnode.cpp b/src/gl/glnode.cpp index a6c910012..53342da79 100644 --- a/src/gl/glnode.cpp +++ b/src/gl/glnode.cpp @@ -306,10 +306,12 @@ void Node::update( const NifModel * nif, const QModelIndex & index ) newProps.add( p ); } - for ( const auto l : nif->getLinkArray( iBlock, "BS Properties" ) ) { - if ( Property * p = scene->getProperty( nif, nif->getBlock( l ) ) ) - newProps.add( p ); - } + if ( Property * p = scene->getProperty( nif, nif->getBlock( nif->getLink( iBlock, "Shader Property" ) ) ) ) + newProps.add( p ); + + if ( Property * p = scene->getProperty( nif, nif->getBlock( nif->getLink( iBlock, "Alpha Property" ) ) ) ) + newProps.add( p ); + properties = newProps; children.clear(); @@ -1138,6 +1140,8 @@ void drawHvkConstraint( const NifModel * nif, const QModelIndex & iConstraint, c if ( name == "bhkLimitedHingeConstraint" ) { QModelIndex iHinge = nif->getIndex( iConstraint, "Limited Hinge" ); + if ( !iHinge.isValid() ) + iHinge = iConstraint; const Vector3 pivotA( nif->get( iHinge, "Pivot A" ) ); const Vector3 pivotB( nif->get( iHinge, "Pivot B" ) ); @@ -1192,6 +1196,8 @@ void drawHvkConstraint( const NifModel * nif, const QModelIndex & iConstraint, c glEnd(); } else if ( name == "bhkHingeConstraint" ) { QModelIndex iHinge = nif->getIndex( iConstraint, "Hinge" ); + if ( !iHinge.isValid() ) + iHinge = iConstraint; const Vector3 pivotA( nif->get( iHinge, "Pivot A" ) ); const Vector3 pivotB( nif->get( iHinge, "Pivot B" ) ); @@ -1260,6 +1266,8 @@ void drawHvkConstraint( const NifModel * nif, const QModelIndex & iConstraint, c drawSpring( pivotA, pivotB, length ); } else if ( name == "bhkRagdollConstraint" ) { QModelIndex iRagdoll = nif->getIndex( iConstraint, "Ragdoll" ); + if ( !iRagdoll.isValid() ) + iRagdoll = iConstraint; const Vector3 pivotA( nif->get( iRagdoll, "Pivot A" ) ); const Vector3 pivotB( nif->get( iRagdoll, "Pivot B" ) ); @@ -1316,8 +1324,8 @@ void drawHvkConstraint( const NifModel * nif, const QModelIndex & iConstraint, c const Vector3 pivotA( nif->get( iConstraint, "Pivot A" ) ); const Vector3 pivotB( nif->get( iConstraint, "Pivot B" ) ); - const Vector3 planeNormal( nif->get( iConstraint, "Plane" ) ); - const Vector3 slidingAxis( nif->get( iConstraint, "Sliding Axis" ) ); + const Vector3 planeNormal( nif->get( iConstraint, "Plane A" ) ); + const Vector3 slidingAxis( nif->get( iConstraint, "Sliding A" ) ); const float minDistance = nif->get( iConstraint, "Min Distance" ); const float maxDistance = nif->get( iConstraint, "Max Distance" ); diff --git a/src/gl/gltexloaders.cpp b/src/gl/gltexloaders.cpp index 9901c7258..089b737ca 100644 --- a/src/gl/gltexloaders.cpp +++ b/src/gl/gltexloaders.cpp @@ -1762,12 +1762,12 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) qDebug() << pix.get( srcChannels.child( i, 0 ), "Type" ); qDebug() << pix.get( srcChannels.child( i, 0 ), "Convention" ); qDebug() << pix.get( srcChannels.child( i, 0 ), "Bits Per Channel" ); - qDebug() << pix.get( srcChannels.child( i, 0 ), "Signed" ); + qDebug() << pix.get( srcChannels.child( i, 0 ), "Is Signed" ); nif->set( destChannels.child( i, 0 ), "Type", pix.get( srcChannels.child( i, 0 ), "Type" ) ); nif->set( destChannels.child( i, 0 ), "Convention", pix.get( srcChannels.child( i, 0 ), "Convention" ) ); nif->set( destChannels.child( i, 0 ), "Bits Per Channel", pix.get( srcChannels.child( i, 0 ), "Bits Per Channel" ) ); - nif->set( destChannels.child( i, 0 ), "Signed", pix.get( srcChannels.child( i, 0 ), "Signed" ) ); + nif->set( destChannels.child( i, 0 ), "Is Signed", pix.get( srcChannels.child( i, 0 ), "Is Signed" ) ); } nif->set( iData, "Num Faces", pix.get( iPixData, "Num Faces" ) ); @@ -1848,7 +1848,7 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) nif->set( destChannels.child( i, 0 ), "Type", i ); // red, green, blue, alpha nif->set( destChannels.child( i, 0 ), "Convention", 0 ); // fixed nif->set( destChannels.child( i, 0 ), "Bits Per Channel", 8 ); - nif->set( destChannels.child( i, 0 ), "Signed", 0 ); + nif->set( destChannels.child( i, 0 ), "Is Signed", 0 ); } } @@ -1990,7 +1990,7 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) } nif->set( destChannels.child( i, 0 ), "Bits Per Channel", 0 ); - nif->set( destChannels.child( i, 0 ), "Signed", 1 ); + nif->set( destChannels.child( i, 0 ), "Is Signed", 1 ); } } else { nif->set( iData, "Bits Per Pixel", ddsHeader.ddsPixelFormat.dwBPP ); @@ -2012,19 +2012,19 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) for ( int i = 0; i < 3; i++ ) { nif->set( destChannels.child( i, 0 ), "Convention", 0 ); // fixed nif->set( destChannels.child( i, 0 ), "Bits Per Channel", 8 ); - nif->set( destChannels.child( i, 0 ), "Signed", 0 ); + nif->set( destChannels.child( i, 0 ), "Is Signed", 0 ); } if ( ddsHeader.ddsPixelFormat.dwBPP == 32 ) { nif->set( destChannels.child( 3, 0 ), "Type", 3 ); // alpha nif->set( destChannels.child( 3, 0 ), "Convention", 0 ); // fixed nif->set( destChannels.child( 3, 0 ), "Bits Per Channel", 8 ); - nif->set( destChannels.child( 3, 0 ), "Signed", 0 ); + nif->set( destChannels.child( 3, 0 ), "Is Signed", 0 ); } else if ( ddsHeader.ddsPixelFormat.dwBPP == 24 ) { nif->set( destChannels.child( 3, 0 ), "Type", 19 ); // empty nif->set( destChannels.child( 3, 0 ), "Convention", 5 ); // empty nif->set( destChannels.child( 3, 0 ), "Bits Per Channel", 0 ); - nif->set( destChannels.child( 3, 0 ), "Signed", 0 ); + nif->set( destChannels.child( 3, 0 ), "Is Signed", 0 ); } } } diff --git a/src/gl/renderer.cpp b/src/gl/renderer.cpp index 8712a279b..43173035b 100644 --- a/src/gl/renderer.cpp +++ b/src/gl/renderer.cpp @@ -164,6 +164,8 @@ bool Renderer::ConditionSingle::eval( const NifModel * nif, const QList().GetFlags(), right.toUInt( nullptr, 0 ) ) ^ invert; return false; } diff --git a/src/io/nifstream.cpp b/src/io/nifstream.cpp index 614511165..62ea35743 100644 --- a/src/io/nifstream.cpp +++ b/src/io/nifstream.cpp @@ -458,7 +458,11 @@ bool NifIStream::read( NifValue & val ) return true; } } - + case NifValue::tBSVertexDesc: + { + *dataStream >> *static_cast(val.val.data); + return (dataStream->status() == QDataStream::Ok); + } case NifValue::tBlob: { if ( val.val.data ) { @@ -762,6 +766,14 @@ bool NifOStream::write( const NifValue & val ) return device->write( string.constData(), string.size() ) == string.size(); } } + case NifValue::tBSVertexDesc: + { + auto d = static_cast(val.val.data); + if ( !d ) + return false; + + return device->write( (char*)&d->desc, 8 ) == 8; + } case NifValue::tBlob: if ( val.val.data ) { @@ -770,7 +782,6 @@ bool NifOStream::write( const NifValue & val ) } return true; - case NifValue::tNone: return true; } @@ -838,6 +849,7 @@ int NifSStream::size( const NifValue & val ) case NifValue::tMatrix4: return 64; case NifValue::tVector2: + case NifValue::tBSVertexDesc: return 8; case NifValue::tColor3: return 12; diff --git a/src/model/basemodel.cpp b/src/model/basemodel.cpp index adc1d0a55..44f50fbf8 100644 --- a/src/model/basemodel.cpp +++ b/src/model/basemodel.cpp @@ -668,7 +668,6 @@ NifItem * BaseModel::getItem( NifItem * item, const QString & name ) const return nullptr; int slash = name.indexOf( "\\" ); - if ( slash > 0 ) { QString left = name.left( slash ); QString right = name.right( name.length() - slash - 1 ); @@ -850,6 +849,9 @@ QVariant BaseModelEval::operator()(const QVariant & v) const if ( i2 && i2->value().isCount() ) return QVariant( i2->value().toCount() ); } else { + if ( sibling->value().type() == NifValue::tBSVertexDesc ) + return QVariant( sibling->value().get().GetFlags() << 4 ); + qDebug() << ("can't convert " + left + " to a count"); } } diff --git a/src/spells/blocks.cpp b/src/spells/blocks.cpp index 2f6a409e6..e99ba790c 100644 --- a/src/spells/blocks.cpp +++ b/src/spells/blocks.cpp @@ -97,13 +97,8 @@ void blockLink( NifModel * nif, const QModelIndex & index, const QModelIndex & i addLink( nif, index, "Effects", nif->getBlockNumber( iBlock ) ); } } else if ( nif->inherits( index, "NiAVObject" ) && nif->inherits( iBlock, "NiProperty" ) ) { - // Skyrim note: this will fail if "Properties" is not enabled - if ( !addLink( nif, index, "Properties", nif->getBlockNumber( iBlock ) ) ) { - // "Properties" was not enabled: try Skyrim style "BS Properties" - if ( nif->inherits( index, "NiGeometry" ) ) { - addLink( nif, index, "BS Properties", nif->getBlockNumber( iBlock ) ); - } - } + // Absent in Bethesda 20.2.0.7 stream version > 34 + addLink( nif, index, "Properties", nif->getBlockNumber( iBlock ) ); } /* * Temporary workaround for non-NiProperty properties diff --git a/src/spells/flags.cpp b/src/spells/flags.cpp index caaeb8b43..e3d3875b1 100644 --- a/src/spells/flags.cpp +++ b/src/spells/flags.cpp @@ -17,10 +17,10 @@ */ //! Edit flags -class spEditFlags final : public Spell +class spEditFlags : public Spell { public: - QString name() const override final { return Spell::tr( "Flags" ); } + QString name() const override { return Spell::tr( "Flags" ); } bool instant() const { return true; } QIcon icon() const { return QIcon( ":/img/flag" ); } @@ -111,12 +111,12 @@ class spEditFlags final : public Spell return None; } - bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final + bool isApplicable( const NifModel * nif, const QModelIndex & index ) override { return queryType( nif, getFlagIndex( nif, index ) ) != None; } - QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final + QModelIndex cast( NifModel * nif, const QModelIndex & index ) override { QModelIndex iFlags = getFlagIndex( nif, index ); @@ -874,7 +874,7 @@ class spEditFlags final : public Spell * \param chk A checkbox that enables or disables this checkbox * \return A pointer to the checkbox */ - QCheckBox * dlgCheck( QVBoxLayout * vbox, const QString & name, QCheckBox * chk = nullptr ) + QCheckBox * spEditFlags::dlgCheck( QVBoxLayout * vbox, const QString & name, QCheckBox * chk = nullptr ) { QCheckBox * box = new QCheckBox( name ); vbox->addWidget( box ); @@ -895,7 +895,7 @@ class spEditFlags final : public Spell * \param chk A checkbox that enables or disables this combobox * \return A pointer to the combobox */ - QComboBox * dlgCombo( QVBoxLayout * vbox, const QString & name, QStringList items, QCheckBox * chk = nullptr ) + QComboBox * spEditFlags::dlgCombo( QVBoxLayout * vbox, const QString & name, QStringList items, QCheckBox * chk = nullptr ) { vbox->addWidget( new QLabel( name ) ); QComboBox * cmb = new QComboBox; @@ -919,7 +919,7 @@ class spEditFlags final : public Spell * \param chk A checkbox that enables or disables this spinbox * \return A pointer to the spinbox */ - QSpinBox * dlgSpin( QVBoxLayout * vbox, const QString & name, int min, int max, QCheckBox * chk = nullptr ) + QSpinBox * spEditFlags::dlgSpin( QVBoxLayout * vbox, const QString & name, int min, int max, QCheckBox * chk = nullptr ) { vbox->addWidget( new QLabel( name ) ); QSpinBox * spn = new QSpinBox; @@ -939,7 +939,7 @@ class spEditFlags final : public Spell * \param dlg The dialog to add buttons to * \param vbox Vertical box layout used by the dialog */ - void dlgButtons( QDialog * dlg, QVBoxLayout * vbox ) + void spEditFlags::dlgButtons( QDialog * dlg, QVBoxLayout * vbox ) { QHBoxLayout * hbox = new QHBoxLayout; vbox->addLayout( hbox ); @@ -955,3 +955,89 @@ class spEditFlags final : public Spell }; REGISTER_SPELL( spEditFlags ) + + +class spEditVertexDesc final : public spEditFlags +{ +public: + QString name() const override final { return Spell::tr( "Vertex Flags" ); } + bool instant() const { return true; } + QIcon icon() const { return QIcon( ":/img/flag" ); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final + { + return nif->inherits( index.parent(), "BSTriShape" ) && nif->itemName( index ) == "Vertex Desc"; + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final + { + auto desc = nif->get( index ); + uint stream = nif->getUserVersion2(); + bool dynamic = nif->inherits( index.parent(), "BSDynamicTriShape" ); + + ushort flags = desc.GetFlags(); + QStringList flagNames { + Spell::tr( "Vertex" ), // VA_POSITION = 0x0, + Spell::tr( "UVs" ), // VA_TEXCOORD0 = 0x1, + Spell::tr( "UVs 2" ), // VA_TEXCOORD1 = 0x2, + Spell::tr( "Normals" ), // VA_NORMAL = 0x3, + Spell::tr( "Tangents" ), // VA_BINORMAL = 0x4, + Spell::tr( "Colors" ), // VA_COLOR = 0x5, + Spell::tr( "Skinned" ), // VA_SKINNING = 0x6, + Spell::tr( "Land Data" ), // VA_LANDDATA = 0x7, + Spell::tr( "Eye Data" ), // VA_EYEDATA = 0x8, + Spell::tr( "" ), + Spell::tr( "Full Precision" ) // 1 << 10 + }; + + QDialog dlg; + QVBoxLayout * vbox = new QVBoxLayout; + dlg.setLayout( vbox ); + + QList chkBoxes; + int x = 0; + for ( const QString& flagName : flagNames ) { + chkBoxes << dlgCheck( vbox, flagName ); + chkBoxes.last()->setChecked( desc.HasFlag( VertexAttribute(x) ) ); + // Hide unused attributes + if ( x == 2 || x == 7 || x == 9 ) + chkBoxes.last()->setHidden( true ); + x++; + } + + dlgButtons( &dlg, vbox ); + + if ( dlg.exec() == QDialog::Accepted ) { + x = 0; + for ( QCheckBox * chk : chkBoxes ) { + if ( chk->isChecked() ) + desc.SetFlag( VertexAttribute(x) ); + else + desc.RemoveFlag( VertexAttribute(x) ); + x++; + } + + // Make sure sizes and offsets in rest of vertexDesc are updated from flags + desc.ResetAttributeOffsets( stream ); + if ( dynamic ) + desc.MakeDynamic(); + + if ( nif->set( index, desc ) ) { + auto iDataSize = nif->getIndex( index.parent(), "Data Size" ); + auto numVerts = nif->get( index.parent(), "Num Vertices" ); + auto numTris = nif->get( index.parent(), "Num Triangles" ); + + if ( iDataSize.isValid() ) + nif->set( iDataSize, desc.GetVertexSize() * numVerts + 6 * numTris ); + + nif->updateArray( index.parent(), "Vertex Data" ); + } + + } + + return index; + } + +}; + +REGISTER_SPELL( spEditVertexDesc ) diff --git a/src/spells/normals.cpp b/src/spells/normals.cpp index d2bfdd0c6..ebae73698 100644 --- a/src/spells/normals.cpp +++ b/src/spells/normals.cpp @@ -34,8 +34,8 @@ class spFaceNormals final : public Spell return iData; if ( nif->isNiBlock( index, { "BSTriShape", "BSMeshLODTriShape", "BSSubIndexTriShape" } ) ) { - auto vf = nif->get( index, "VF" ); - if ( (vf & 0x400) && nif->getUserVersion2() == 100 ) { + auto vf = nif->get( index, "Vertex Desc" ); + if ( (vf & VertexFlags::VF_SKINNED) && nif->getUserVersion2() == 100 ) { // Skinned SSE auto skinID = nif->getLink( nif->getIndex( index, "Skin" ) ); auto partID = nif->getLink( nif->getBlock( skinID, "NiSkinInstance" ), "Skin Partition" ); @@ -103,8 +103,8 @@ class spFaceNormals final : public Spell } else { QVector triangles; int numVerts; - auto vf = nif->get( index, "VF" ); - if ( !((vf & 0x400) && nif->getUserVersion2() == 100) ) { + auto vf = nif->get( index, "Vertex Desc" ); + if ( !((vf & VertexFlags::VF_SKINNED) && nif->getUserVersion2() == 100) ) { numVerts = nif->get( index, "Num Vertices" ); triangles = nif->getArray( index, "Triangles" ); } else { @@ -200,8 +200,8 @@ class spSmoothNormals final : public Spell verts = nif->getArray( iData, "Vertices" ); norms = nif->getArray( iData, "Normals" ); } else { - auto vf = nif->get( index, "VF" ); - if ( !((vf & 0x400) && nif->getUserVersion2() == 100) ) { + auto vf = nif->get( index, "Vertex Desc" ); + if ( !((vf & VertexFlags::VF_SKINNED) && nif->getUserVersion2() == 100) ) { numVerts = nif->get( index, "Num Vertices" ); } else { // Skinned SSE diff --git a/src/spells/tangentspace.cpp b/src/spells/tangentspace.cpp index e26b16a12..e28b25d8d 100644 --- a/src/spells/tangentspace.cpp +++ b/src/spells/tangentspace.cpp @@ -46,8 +46,8 @@ QModelIndex spTangentSpace::cast( NifModel * nif, const QModelIndex & iBlock ) if ( nif->getUserVersion2() < 100 ) { iData = nif->getBlock( nif->getLink( iShape, "Data" ) ); } else { - auto vf = nif->get( iShape, "VF" ); - if ( (vf & 0x400) && nif->getUserVersion2() == 100 ) { + auto vf = nif->get( iShape, "Vertex Desc" ); + if ( (vf & VertexFlags::VF_SKINNED) && nif->getUserVersion2() == 100 ) { // Skinned SSE auto skinID = nif->getLink( nif->getIndex( iShape, "Skin" ) ); auto partID = nif->getLink( nif->getBlock( skinID, "NiSkinInstance" ), "Skin Partition" ); diff --git a/src/spells/texture.cpp b/src/spells/texture.cpp index 44c0acb28..b1c4171ad 100644 --- a/src/spells/texture.cpp +++ b/src/spells/texture.cpp @@ -627,8 +627,8 @@ class spTextureTemplate final : public Spell if ( nif->getUserVersion2() >= 100 ) { QModelIndex iVertData; - auto vf = nif->get( index, "VF" ); - if ( (vf & 0x400) && nif->getUserVersion2() == 100 ) { + auto vf = nif->get( index, "Vertex Desc" ); + if ( (vf & VertexFlags::VF_SKINNED) && nif->getUserVersion2() == 100 ) { // Skinned SSE auto skinID = nif->getLink( nif->getIndex( index, "Skin" ) ); auto partID = nif->getLink( nif->getBlock( skinID, "NiSkinInstance" ), "Skin Partition" ); diff --git a/src/ui/widgets/uvedit.cpp b/src/ui/widgets/uvedit.cpp index 5261158ff..1360e7a53 100644 --- a/src/ui/widgets/uvedit.cpp +++ b/src/ui/widgets/uvedit.cpp @@ -833,8 +833,8 @@ bool UVWidget::setNifData( NifModel * nifModel, const QModelIndex & nifIndex ) if ( nif->getVersionNumber() == 0x14020007 && nif->getUserVersion2() >= 100 ) { iShapeData = nif->getIndex( iShape, "Vertex Data" ); - auto vf = nif->get( iShape, "VF" ); - if ( (vf & 0x400) && nif->getUserVersion2() == 100 ) { + auto vf = nif->get( iShape, "Vertex Desc" ); + if ( (vf & VertexFlags::VF_SKINNED) && nif->getUserVersion2() == 100 ) { // Skinned SSE auto skinID = nif->getLink( nif->getIndex( iShape, "Skin" ) ); auto partID = nif->getLink( nif->getBlock( skinID, "NiSkinInstance" ), "Skin Partition" ); @@ -876,9 +876,9 @@ bool UVWidget::setNifData( NifModel * nifModel, const QModelIndex & nifIndex ) return false; } - for ( const auto l : - nif->getLinkArray( iShape, "Properties" ) - + nif->getLinkArray( iShape, "BS Properties" ) ) + auto props = nif->getLinkArray( iShape, "Properties" ); + props << nif->getLink( iShape, "Shader Property" ); + for ( const auto l : props ) { QModelIndex iTexProp = nif->getBlock( l, "NiTexturingProperty" ); @@ -1508,9 +1508,10 @@ void UVWidget::getTexSlots() { menuTexSelect->clear(); validTexs.clear(); - for ( const auto l : - nif->getLinkArray( iShape, "Properties" ) - + nif->getLinkArray( iShape, "BS Properties" ) ) + + auto props = nif->getLinkArray( iShape, "Properties" ); + props << nif->getLink( iShape, "Shader Property" ); + for ( const auto l : props ) { QModelIndex iTexProp = nif->getBlock( l, "NiTexturingProperty" ); @@ -1538,9 +1539,10 @@ void UVWidget::selectTexSlot() { QString selected = texSlotGroup->checkedAction()->text(); currentTexSlot = texnames.indexOf( selected ); - for ( const auto l : - nif->getLinkArray( iShape, "Properties" ) - + nif->getLinkArray( iShape, "BS Properties" ) ) + + auto props = nif->getLinkArray( iShape, "Properties" ); + props << nif->getLink( iShape, "Shader Property" ); + for ( const auto l : props ) { QModelIndex iTexProp = nif->getBlock( l, "NiTexturingProperty" ); From f43ec649034e0f2fbe30e15c31abe38813cdc4cc Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sun, 15 Oct 2017 20:13:09 -0400 Subject: [PATCH 081/152] [GL] Enable shaders for non-Windows I tested shaders in Ubuntu and it works fine. --- src/gl/renderer.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/gl/renderer.cpp b/src/gl/renderer.cpp index 43173035b..e880325d8 100644 --- a/src/gl/renderer.cpp +++ b/src/gl/renderer.cpp @@ -413,8 +413,6 @@ void Renderer::updateShaders() dir.cd( "/usr/share/nifskope/shaders" ); #endif -// linux does not want to load the shaders so disable them for now -#ifdef WIN32 dir.setNameFilters( { "*.vert" } ); for ( const QString& name : dir.entryList() ) { Shader * shader = new Shader( name, GL_VERTEX_SHADER, fn ); @@ -435,7 +433,6 @@ void Renderer::updateShaders() program->load( dir.filePath( name ), this ); programs.insert( name, program ); } -#endif } void Renderer::releaseShaders() From de65fa8029531dfe4d92904189ae925f030e92da Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sun, 15 Oct 2017 20:50:59 -0400 Subject: [PATCH 082/152] [Build] Cross-platform fixes Knew about this from building locally on Linux but forgot to commit. --- src/spells/flags.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/spells/flags.cpp b/src/spells/flags.cpp index e3d3875b1..b4e79c449 100644 --- a/src/spells/flags.cpp +++ b/src/spells/flags.cpp @@ -874,7 +874,7 @@ class spEditFlags : public Spell * \param chk A checkbox that enables or disables this checkbox * \return A pointer to the checkbox */ - QCheckBox * spEditFlags::dlgCheck( QVBoxLayout * vbox, const QString & name, QCheckBox * chk = nullptr ) + QCheckBox * dlgCheck( QVBoxLayout * vbox, const QString & name, QCheckBox * chk = nullptr ) { QCheckBox * box = new QCheckBox( name ); vbox->addWidget( box ); @@ -895,7 +895,7 @@ class spEditFlags : public Spell * \param chk A checkbox that enables or disables this combobox * \return A pointer to the combobox */ - QComboBox * spEditFlags::dlgCombo( QVBoxLayout * vbox, const QString & name, QStringList items, QCheckBox * chk = nullptr ) + QComboBox * dlgCombo( QVBoxLayout * vbox, const QString & name, QStringList items, QCheckBox * chk = nullptr ) { vbox->addWidget( new QLabel( name ) ); QComboBox * cmb = new QComboBox; @@ -919,7 +919,7 @@ class spEditFlags : public Spell * \param chk A checkbox that enables or disables this spinbox * \return A pointer to the spinbox */ - QSpinBox * spEditFlags::dlgSpin( QVBoxLayout * vbox, const QString & name, int min, int max, QCheckBox * chk = nullptr ) + QSpinBox * dlgSpin( QVBoxLayout * vbox, const QString & name, int min, int max, QCheckBox * chk = nullptr ) { vbox->addWidget( new QLabel( name ) ); QSpinBox * spn = new QSpinBox; @@ -939,7 +939,7 @@ class spEditFlags : public Spell * \param dlg The dialog to add buttons to * \param vbox Vertical box layout used by the dialog */ - void spEditFlags::dlgButtons( QDialog * dlg, QVBoxLayout * vbox ) + void dlgButtons( QDialog * dlg, QVBoxLayout * vbox ) { QHBoxLayout * hbox = new QHBoxLayout; vbox->addLayout( hbox ); From 16a3019b2e7659bc262a90e2f437f5b8d2204f1f Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sat, 21 Oct 2017 23:36:30 -0400 Subject: [PATCH 083/152] [UI] NifBlockEditor reject changes button Adds button which resets the state of all the NifEditBox items in the editor to when it was first created. This allows you to undo all changes while the editor was open. --- src/ui/widgets/nifeditors.cpp | 20 +++++++++++++++++++- src/ui/widgets/nifeditors.h | 10 ++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/ui/widgets/nifeditors.cpp b/src/ui/widgets/nifeditors.cpp index 06fd5cfd6..2a9295479 100644 --- a/src/ui/widgets/nifeditors.cpp +++ b/src/ui/widgets/nifeditors.cpp @@ -63,9 +63,17 @@ NifBlockEditor::NifBlockEditor( NifModel * n, const QModelIndex & i, bool fireAn if ( fireAndForget ) { setAttribute( Qt::WA_DeleteOnClose ); + QHBoxLayout * btnlayout = new QHBoxLayout(); + QPushButton * btAccept = new QPushButton( tr( "Accept" ) ); connect( btAccept, &QPushButton::clicked, this, &NifBlockEditor::close ); - layout->addWidget( btAccept ); + btnlayout->addWidget( btAccept ); + + QPushButton * btReject = new QPushButton( tr( "Reject" ) ); + connect( btReject, &QPushButton::clicked, this, &NifBlockEditor::reset ); + btnlayout->addWidget( btReject ); + + layout->addLayout( btnlayout ); } setWindowFlags( Qt::Tool | Qt::WindowStaysOnTopHint ); @@ -79,6 +87,8 @@ void NifBlockEditor::add( NifEditBox * box ) layouts.top()->insertWidget( layouts.top()->count() - 1, box ); else layouts.top()->addWidget( box ); + + connect( this, &NifBlockEditor::reset, box, &NifEditBox::sltReset ); } void NifBlockEditor::pushLayout( QBoxLayout * lay, const QString & name ) @@ -164,6 +174,8 @@ NifEditBox::NifEditBox( NifModel * n, const QModelIndex & i ) : QGroupBox(), nif( n ), index( i ) { setTitle( nif->itemName( index ) ); + + originalValue = n->getValue( i ); } QLayout * NifEditBox::getLayout() @@ -178,6 +190,12 @@ QLayout * NifEditBox::getLayout() return lay; } +void NifEditBox::sltReset() +{ + if ( nif && index.isValid() ) + nif->setValue( index, originalValue ); +} + void NifEditBox::sltApplyData() { if ( nif && index.isValid() ) { diff --git a/src/ui/widgets/nifeditors.h b/src/ui/widgets/nifeditors.h index d63f2eb10..dda8d6df9 100644 --- a/src/ui/widgets/nifeditors.h +++ b/src/ui/widgets/nifeditors.h @@ -33,6 +33,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef NIFEDITORS_H #define NIFEDITORS_H +#include "data/nifvalue.h" + #include // Inherited #include // Inherited #include @@ -55,6 +57,9 @@ class NifEditBox : public QGroupBox QModelIndex getIndex() const { return index; } +public slots: + void sltReset(); + protected slots: void sltApplyData(); @@ -63,6 +68,8 @@ protected slots: QPointer nif; QPersistentModelIndex index; + + NifValue originalValue; }; class NifBlockEditor final : public QWidget @@ -79,6 +86,9 @@ class NifBlockEditor final : public QWidget NifModel * getNif() { return nif; } +signals: + void reset(); + protected slots: void nifDataChanged( const QModelIndex &, const QModelIndex & ); void nifDestroyed(); From 1764db85833ea48842453f973cde39464b63cba9 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sun, 22 Oct 2017 00:45:12 -0400 Subject: [PATCH 084/152] [Imp/Ex] Slight cleanup, expanded support Re-enable the Import menu but only conditionally based on NIF version. For unsupported NIF versions either or both of the Import/Export menus will disable. Fixed MTL file writing for Export for Skyrim and later. Changed uses of == NiNode to inherits NiNode to correctly support BSFadeNode. Automatically run Update Tangent Spaces on OBJ Import. --- src/lib/importex/importex.cpp | 23 +++++++++++++---- src/lib/importex/obj.cpp | 47 +++++++++++++++++++++++++---------- src/nifskope_ui.cpp | 11 ++++++++ src/ui/nifskope.ui | 2 +- 4 files changed, 64 insertions(+), 19 deletions(-) diff --git a/src/lib/importex/importex.cpp b/src/lib/importex/importex.cpp index c33e9e296..a253c9c34 100644 --- a/src/lib/importex/importex.cpp +++ b/src/lib/importex/importex.cpp @@ -51,7 +51,7 @@ void NifSkope::fillImportExportMenus() { mExport->addAction( tr( "Export .OBJ" ) ); //mExport->addAction( tr( "Export .DAE" ) ); - mImport->addAction( tr( "Import .3DS" ) ); + //mImport->addAction( tr( "Import .3DS" ) ); mImport->addAction( tr( "Import .OBJ" ) ); } @@ -75,12 +75,25 @@ void NifSkope::sltImportExport( QAction * a ) } } + if ( nif && nif->getVersionNumber() >= 0x14050000 ) { + mExport->setDisabled( true ); + mImport->setDisabled( true ); + return; + } else { + if ( nif->getUserVersion2() >= 100 ) + mImport->setDisabled( true ); + else + mImport->setDisabled( false ); + + mExport->setDisabled( false ); + } + if ( a->text() == tr( "Export .OBJ" ) ) exportObj( nif, index ); else if ( a->text() == tr( "Import .OBJ" ) ) importObj( nif, index ); - else if ( a->text() == tr( "Import .3DS" ) ) - import3ds( nif, index ); - else if ( a->text() == tr( "Export .DAE" ) ) - exportCol( nif, currentFile ); + //else if ( a->text() == tr( "Import .3DS" ) ) + // import3ds( nif, index ); + //else if ( a->text() == tr( "Export .DAE" ) ) + // exportCol( nif, currentFile ); } diff --git a/src/lib/importex/obj.cpp b/src/lib/importex/obj.cpp index a0cbad69f..e4ff3b169 100644 --- a/src/lib/importex/obj.cpp +++ b/src/lib/importex/obj.cpp @@ -33,6 +33,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "message.h" #include "gl/gltex.h" #include "model/nifmodel.h" +#include "spells/tangentspace.h" #include "lib/nvtristripwrapper.h" @@ -61,7 +62,7 @@ static void writeData( const NifModel * nif, const QModelIndex & iData, QTextStr { // copy vertices - if ( nif->getUserVersion2() < 130 ) { + if ( nif->getUserVersion2() < 100 ) { QVector verts = nif->getArray( iData, "Vertices" ); foreach( Vector3 v, verts ) { @@ -198,7 +199,7 @@ static void writeData( const NifModel * nif, const QModelIndex & iData, QTextStr static void writeShape( const NifModel * nif, const QModelIndex & iShape, QTextStream & obj, QTextStream & mtl, int ofs[], Transform t ) { QString name = nif->get( iShape, "Name" ); - QString matn = name, map_Kd, map_Ks, map_Ns, map_d, disp, decal, bump; + QString matn = name, map_Kd, map_Kn, map_Ks, map_Ns, map_d, disp, decal, bump; Color3 mata, matd, mats; float matt = 1.0, matg = 33.0; @@ -239,9 +240,24 @@ static void writeShape( const NifModel * nif, const QModelIndex & iShape, QTextS ); } else if ( nif->isNiBlock( iProp, { "BSShaderNoLightingProperty", "SkyShaderProperty", "TileShaderProperty" } ) ) { map_Kd = TexCache::find( nif->get( iProp, "File Name" ), nif->getFolder() ); - } else if ( nif->isNiBlock( iProp, { "BSShaderPPLightingProperty", "Lighting30ShaderProperty" } ) ) { + } else if ( nif->isNiBlock( iProp, "BSEffectShaderProperty" ) ) { + map_Kd = TexCache::find( nif->get( iProp, "Source Texture" ), nif->getFolder() ); + } else if ( nif->isNiBlock( iProp, { "BSShaderPPLightingProperty", "Lighting30ShaderProperty", "BSLightingShaderProperty" } ) ) { QModelIndex iArray = nif->getIndex( nif->getBlock( nif->getLink( iProp, "Texture Set" ) ), "Textures" ); map_Kd = TexCache::find( nif->get( iArray.child( 0, 0 ) ), nif->getFolder() ); + map_Kn = TexCache::find( nif->get( iArray.child( 1, 0 ) ), nif->getFolder() ); + + auto iSpec = nif->getIndex( iProp, "Specular Color" ); + if ( iSpec.isValid() ) + mats = nif->get( iSpec ); + + auto iAlpha = nif->getIndex( iProp, "Alpha" ); + if ( iAlpha.isValid() ) + matt = nif->get( iAlpha ); + + auto iGlossiness = nif->getIndex( iProp, "Glossiness" ); + if ( iGlossiness.isValid() ) + matg = nif->get( iGlossiness ); } } @@ -259,17 +275,20 @@ static void writeShape( const NifModel * nif, const QModelIndex & iShape, QTextS mtl << "Ns " << matg << "\r\n"; if ( !map_Kd.isEmpty() ) - mtl << "map_Kd " << map_Kd << "\r\n\r\n"; + mtl << "map_Kd " << map_Kd << "\r\n"; + + if ( !map_Kn.isEmpty() ) + mtl << "map_Kn " << map_Kn << "\r\n"; if ( !decal.isEmpty() ) - mtl << "decal " << decal << "\r\n\r\n"; + mtl << "decal " << decal << "\r\n"; if ( !bump.isEmpty() ) - mtl << "bump " << decal << "\r\n\r\n"; + mtl << "bump " << decal << "\r\n"; obj << "\r\n# " << name << "\r\n\r\ng " << name << "\r\n" << "usemtl " << matn << "\r\n\r\n"; - if ( nif->getUserVersion2() < 130 ) + if ( nif->getUserVersion2() < 100 ) writeData( nif, nif->getBlock( nif->getLink( iShape, "Data" ) ), obj, ofs, t ); else writeData( nif, iShape, obj, ofs, t ); @@ -372,7 +391,7 @@ void exportObj( const NifModel * nif, const QModelIndex & index ) if ( iBlock.isValid() ) { roots.append( nif->getBlockNumber( index ) ); - if ( nif->itemName( index ) == "NiNode" ) { + if ( nif->inherits( index, "NiNode" ) ) { question = tr( "NiNode selected. All children of selected node will be exported." ); } else if ( nif->itemName( index ) == "NiTriShape" || nif->itemName( index ) == "NiTriStrips" ) { question = nif->itemName( index ) + tr( " selected. Selected mesh will be exported." ); @@ -555,7 +574,7 @@ void importObj( NifModel * nif, const QModelIndex & index ) return; } - if ( iBlock.isValid() && nif->itemName( iBlock ) == "NiNode" ) { + if ( iBlock.isValid() && nif->inherits( iBlock, "NiNode" ) ) { iNode = iBlock; } else if ( iBlock.isValid() && nif->itemName( iBlock ) == "NiTriShape" ) { iShape = iBlock; @@ -885,10 +904,9 @@ void importObj( NifModel * nif, const QModelIndex & index ) nif->updateArray( iData, "Normals" ); nif->setArray( iData, "Normals", norms ); nif->set( iData, "Has UV", 1 ); - int cNumUVSets = nif->get( iData, "Num UV Sets" );// keep things the way they are - nif->set( iData, "Num UV Sets", 1 | cNumUVSets );// keep things the way they are - nif->set( iData, "Num UV Sets 2", 1 | cNumUVSets );// keep things the way they are - nif->set( iData, "BS Num UV Sets", 4097 ); + nif->set( iData, "Num UV Sets", 1 ); + nif->set( iData, "Vector Flags", 4097 ); + nif->set( iData, "BS Vector Flags", 4097 ); QModelIndex iTexCo = nif->getIndex( iData, "UV Sets" ); if ( !iTexCo.isValid() ) @@ -1050,6 +1068,9 @@ void importObj( NifModel * nif, const QModelIndex & index ) nif->setLink( iNode, "Collision Object", nif->getBlockNumber( iObject ) ); } + spTangentSpace TSpacer; + TSpacer.castIfApplicable( nif, iShape ); + //Finished with the first shape which is the only one that can import over the top of existing data first_tri_shape = false; } diff --git a/src/nifskope_ui.cpp b/src/nifskope_ui.cpp index 8f7abca42..c363e7e89 100644 --- a/src/nifskope_ui.cpp +++ b/src/nifskope_ui.cpp @@ -762,6 +762,17 @@ void NifSkope::onLoadComplete( bool success, QString & fname ) { QApplication::restoreOverrideCursor(); + if ( nif && nif->getVersionNumber() >= 0x14050000 ) { + mExport->setDisabled( true ); + mImport->setDisabled( true ); + } else { + mExport->setDisabled( false ); + if ( nif->getUserVersion2() >= 100 ) + mImport->setDisabled( true ); + else + mImport->setDisabled( false ); + } + // Reconnect the models to the views swapModels(); diff --git a/src/ui/nifskope.ui b/src/ui/nifskope.ui index 3b3783742..f9c5d4d7f 100644 --- a/src/ui/nifskope.ui +++ b/src/ui/nifskope.ui @@ -288,7 +288,7 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 - false + true Import From c8c2a5a0c572dbd42425be9b962a8b027961e9cf Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sun, 22 Oct 2017 21:56:17 -0400 Subject: [PATCH 085/152] [GL] Reworked FO4 segment vis Shows more info at once such as when the Segment array is selected it will show all segments at once. --- src/gl/bsshape.cpp | 88 +++++++++++++++++++++++++++++++--------------- 1 file changed, 60 insertions(+), 28 deletions(-) diff --git a/src/gl/bsshape.cpp b/src/gl/bsshape.cpp index 7197b6dc0..4957981df 100644 --- a/src/gl/bsshape.cpp +++ b/src/gl/bsshape.cpp @@ -712,15 +712,6 @@ void BSShape::drawSelection() const if ( n == "Segment" || n == "Sub Segment" || n == "Num Primitives" ) { auto sidx = idx; int s; - if ( n != "Num Primitives" ) { - sidx = idx.child( 1, 0 ); - } - s = sidx.row(); - - auto off = sidx.sibling( s - 1, 2 ).data().toInt() / 3; - auto cnt = sidx.sibling( s, 2 ).data().toInt(); - - auto numRec = sidx.sibling( s + 2, 2 ).data().toInt(); QVector cols = { { 255, 0, 0, 128 }, { 0, 255, 0, 128 }, { 0, 0, 255, 128 }, { 255, 255, 0, 128 }, { 0, 255, 255, 128 }, { 255, 0, 255, 128 }, { 255, 255, 255, 128 } @@ -728,21 +719,58 @@ void BSShape::drawSelection() const glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + auto type = idx.sibling( idx.row(), 1 ).data( Qt::DisplayRole ).toString(); + + bool isSegmentArray = (n == "Segment" && type == "BSGeometrySegmentData" && nif->isArray( idx )); + bool isSegmentItem = (n == "Segment" && type == "BSGeometrySegmentData" && !nif->isArray( idx )); + bool isSubSegArray = (n == "Sub Segment" && nif->isArray( idx )); + + int off = 0; + int cnt = 0; + int numRec = 0; + + int o = 0; + if ( isSegmentItem || isSegmentArray ) { + o = 3; // Offset 3 rows for < 130 BSGeometrySegmentData + } else if ( isSubSegArray ) { + o = -3; // Look 3 rows above for Sub Seg Array info + } + int maxTris = triangles.count(); - if ( numRec > 0 ) { + int loopNum = 1; + if ( isSegmentArray ) + loopNum = nif->rowCount( idx ); + + for ( int l = 0; l < loopNum; l++ ) { + + if ( n != "Num Primitives" && !isSubSegArray && !isSegmentArray ) { + sidx = idx.child( 1, 0 ); + } else if ( isSegmentArray ) { + sidx = idx.child( l, 0 ).child( 1, 0 ); + } + s = sidx.row() + o; + + off = sidx.sibling( s - 1, 2 ).data().toInt() / 3; + cnt = sidx.sibling( s, 2 ).data().toInt(); + numRec = sidx.sibling( s + 2, 2 ).data().toInt(); + auto recs = sidx.sibling( s + 3, 0 ); for ( int i = 0; i < numRec; i++ ) { auto subrec = recs.child( i, 0 ); - auto off = subrec.child( 0, 2 ).data().toInt() / 3; - auto cnt = subrec.child( 1, 2 ).data().toInt(); + int o = 0; + if ( subrec.data( Qt::DisplayRole ).toString() != "Sub Segment" ) + o = 3; // Offset 3 rows for < 130 BSGeometrySegmentData - int j = off; - for ( j; j < cnt + off; j++ ) { + auto suboff = subrec.child( o, 2 ).data().toInt() / 3; + auto subcnt = subrec.child( o + 1, 2 ).data().toInt(); + + int j = suboff; + for ( j; j < subcnt + suboff; j++ ) { if ( j >= maxTris ) continue; - glColor( Color4(cols.value( i % 7 )) ); + glColor( Color4( cols.value( i % 7 ) ) ); Triangle tri = triangles[j]; glBegin( GL_TRIANGLES ); glVertex( transVerts.value( tri.v1() ) ); @@ -751,22 +779,26 @@ void BSShape::drawSelection() const glEnd(); } } - } else { - glColor( Color4(cols.value( idx.row() % 7 )) ); - - int i = off; - for ( i; i < cnt + off; i++ ) { - if ( i >= maxTris ) - continue; - Triangle tri = triangles[i]; - glBegin( GL_TRIANGLES ); - glVertex( transVerts.value( tri.v1() ) ); - glVertex( transVerts.value( tri.v2() ) ); - glVertex( transVerts.value( tri.v3() ) ); - glEnd(); + // Sub-segmentless Segments + if ( numRec == 0 && cnt > 0 ) { + glColor( Color4( cols.value( (idx.row() + l) % 7 ) ) ); + + int i = off; + for ( i; i < cnt + off; i++ ) { + if ( i >= maxTris ) + continue; + + Triangle tri = triangles[i]; + glBegin( GL_TRIANGLES ); + glVertex( transVerts.value( tri.v1() ) ); + glVertex( transVerts.value( tri.v2() ) ); + glVertex( transVerts.value( tri.v3() ) ); + glEnd(); + } } } + pop(); return; } From c9fa4d66dab63cab4b0166c59045fa50b9714d18 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Mon, 23 Oct 2017 14:32:46 -0400 Subject: [PATCH 086/152] [GL] Fix msn .prog logic from e8b7127 The sk_msn.prog was incorrectly applying to non-MSN meshes such as parallax and multi-layer parallax. --- res/shaders/sk_msn.prog | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/res/shaders/sk_msn.prog b/res/shaders/sk_msn.prog index c2423bcf5..2be16599f 100644 --- a/res/shaders/sk_msn.prog +++ b/res/shaders/sk_msn.prog @@ -12,10 +12,14 @@ checkgroup begin or # Skyrim checkgroup begin and check BSLightingShaderProperty - checkgroup begin or - check NiTriBasedGeomData/Has Normals == 0 - check not BSTriShape/Vertex Desc & 8 - checkgroup end + check NiTriBasedGeomData + check NiTriBasedGeomData/Has Normals == 0 + checkgroup end + # SSE + checkgroup begin and + check BSLightingShaderProperty + check BSTriShape + check not BSTriShape/Vertex Desc & 8 checkgroup end checkgroup end From d8f2724bd13bb0dfb5f70b7697c0d92b7bcfd0f8 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Fri, 27 Oct 2017 22:12:09 -0400 Subject: [PATCH 087/152] nifxml 0.9 sync, Havok constraint fixes Completed changes for the Havok constraint descriptor changes from before 0.9 and also extended support to more constraint types for the vis/spells. Fixed swapped names from 5d8c33a for BSpline interps. Restored copy/paste property and Attach Property behavior from BS Properties removal. Fixed hex integer parsing in conditions. Fixed evaluation of floats as integers for float to hex byte comparisons in conditions. --- src/gl/glcontroller.cpp | 12 ++++----- src/gl/glnode.cpp | 16 +++++++++--- src/gl/glproperty.cpp | 3 +++ src/model/basemodel.cpp | 2 +- src/spells/blocks.cpp | 41 +++++++++++++++++++------------ src/spells/havok.cpp | 54 ++++++++++++++++++++++++++++------------- src/xml/nifexpr.cpp | 2 ++ 7 files changed, 87 insertions(+), 43 deletions(-) diff --git a/src/gl/glcontroller.cpp b/src/gl/glcontroller.cpp index 547fbea4a..2c84ef2ff 100644 --- a/src/gl/glcontroller.cpp +++ b/src/gl/glcontroller.cpp @@ -768,12 +768,12 @@ bool BSplineTransformInterpolator::update( const NifModel * nif, const QModelInd lTransOff = nif->get( index, "Translation Handle" ); lRotateOff = nif->get( index, "Rotation Handle" ); lScaleOff = nif->get( index, "Scale Handle" ); - lTransMult = nif->get( index, "Translation Offset" ); - lRotateMult = nif->get( index, "Rotation Offset" ); - lScaleMult = nif->get( index, "Scale Offset" ); - lTransBias = nif->get( index, "Translation Half Range" ); - lRotateBias = nif->get( index, "Rotation Half Range" ); - lScaleBias = nif->get( index, "Scale Half Range" ); + lTransMult = nif->get( index, "Translation Half Range" ); + lRotateMult = nif->get( index, "Rotation Half Range" ); + lScaleMult = nif->get( index, "Scale Half Range" ); + lTransBias = nif->get( index, "Translation Offset" ); + lRotateBias = nif->get( index, "Rotation Offset" ); + lScaleBias = nif->get( index, "Scale Offset" ); return true; } diff --git a/src/gl/glnode.cpp b/src/gl/glnode.cpp index 53342da79..352b2a349 100644 --- a/src/gl/glnode.cpp +++ b/src/gl/glnode.cpp @@ -1130,11 +1130,15 @@ void drawHvkConstraint( const NifModel * nif, const QModelIndex & iConstraint, c QString name = nif->itemName( iConstraint ); - if ( name == "bhkMalleableConstraint" ) { + if ( name == "bhkMalleableConstraint" || name == "bhkBreakableConstraint" ) { if ( nif->getIndex( iConstraint, "Ragdoll" ).isValid() ) { name = "bhkRagdollConstraint"; } else if ( nif->getIndex( iConstraint, "Limited Hinge" ).isValid() ) { name = "bhkLimitedHingeConstraint"; + } else if ( nif->getIndex( iConstraint, "Hinge" ).isValid() ) { + name = "bhkHingeConstraint"; + } else if ( nif->getIndex( iConstraint, "Stiff Spring" ).isValid() ) { + name = "bhkStiffSpringConstraint"; } } @@ -1256,9 +1260,13 @@ void drawHvkConstraint( const NifModel * nif, const QModelIndex & iConstraint, c glBegin( GL_LINES ); glVertex( pivotB ); glVertex( pivotB + axleB ); glEnd(); drawSolidArc( pivotB, axleB / 7, axleB2, axleB1, minAngle, maxAngle, 1.01f, 16 ); } else if ( name == "bhkStiffSpringConstraint" ) { - const Vector3 pivotA = tBodies.value( 0 ) * Vector3( nif->get( iConstraint, "Pivot A" ) ); - const Vector3 pivotB = tBodies.value( 1 ) * Vector3( nif->get( iConstraint, "Pivot B" ) ); - const float length = nif->get( iConstraint, "Length" ); + QModelIndex iSpring = nif->getIndex( iConstraint, "Stiff Spring" ); + if ( !iSpring.isValid() ) + iSpring = iConstraint; + + const Vector3 pivotA = tBodies.value( 0 ) * Vector3( nif->get( iSpring, "Pivot A" ) ); + const Vector3 pivotB = tBodies.value( 1 ) * Vector3( nif->get( iSpring, "Pivot B" ) ); + const float length = nif->get( iSpring, "Length" ); if ( !Node::SELECTING ) glColor( color_b ); diff --git a/src/gl/glproperty.cpp b/src/gl/glproperty.cpp index 9297a0307..c5153351f 100644 --- a/src/gl/glproperty.cpp +++ b/src/gl/glproperty.cpp @@ -1132,6 +1132,9 @@ void BSLightingShaderProperty::updateParams( const NifModel * nif, const QModelI if ( hasSF1( ShaderFlags::SLSF1_Specular ) ) { auto spC = nif->get( prop, "Specular Color" ); auto spG = nif->get( prop, "Glossiness" ); + // FO4 + if ( spG == 0.0 ) + spG = nif->get( prop, "Smoothness" ); auto spS = nif->get( prop, "Specular Strength" ); setSpecular( spC, spG, spS ); } else { diff --git a/src/model/basemodel.cpp b/src/model/basemodel.cpp index 44f50fbf8..686ed2a83 100644 --- a/src/model/basemodel.cpp +++ b/src/model/basemodel.cpp @@ -834,7 +834,7 @@ QVariant BaseModelEval::operator()(const QVariant & v) const const NifItem * sibling = model->getItem( i->parent(), left ); if ( sibling ) { - if ( sibling->value().isCount() ) { + if ( sibling->value().isCount() || sibling->value().isFloat() ) { return QVariant( sibling->value().toCount() ); } else if ( sibling->value().isFileVersion() ) { return QVariant( sibling->value().toFileVersion() ); diff --git a/src/spells/blocks.cpp b/src/spells/blocks.cpp index e99ba790c..6dd8ef8ee 100644 --- a/src/spells/blocks.cpp +++ b/src/spells/blocks.cpp @@ -97,16 +97,14 @@ void blockLink( NifModel * nif, const QModelIndex & index, const QModelIndex & i addLink( nif, index, "Effects", nif->getBlockNumber( iBlock ) ); } } else if ( nif->inherits( index, "NiAVObject" ) && nif->inherits( iBlock, "NiProperty" ) ) { - // Absent in Bethesda 20.2.0.7 stream version > 34 - addLink( nif, index, "Properties", nif->getBlockNumber( iBlock ) ); - } - /* - * Temporary workaround for non-NiProperty properties - */ - else if ( nif->getBlockName( iBlock ) == "BSLightingShaderProperty" ) { - addLink( nif, index, "Properties", nif->getBlockNumber( iBlock ) ); - } else if ( nif->inherits( iBlock, "BSShaderProperty" ) ) { - addLink( nif, index, "Properties", nif->getBlockNumber( iBlock ) ); + if ( !addLink( nif, index, "Properties", nif->getBlockNumber( iBlock ) ) ) { + // Absent in Bethesda 20.2.0.7 stream version > 34 + if ( nif->inherits( nif->getBlockName( iBlock ), "BSShaderProperty" ) ) { + nif->setLink( index, "Shader Property", nif->getBlockNumber( iBlock ) ); + } else if ( nif->getBlockName( iBlock ) == "NiAlphaProperty" ) { + nif->setLink( index, "Alpha Property", nif->getBlockNumber( iBlock ) ); + } + } } else if ( nif->inherits( index, "NiAVObject" ) && nif->inherits( iBlock, "NiExtraData" ) ) { addLink( nif, index, "Extra Data List", nif->getBlockNumber( iBlock ) ); } else if ( nif->inherits( index, "NiObjectNET" ) && nif->inherits( iBlock, "NiTimeController" ) ) { @@ -264,8 +262,8 @@ class spAttachProperty final : public Spell if ( nif->getUserVersion() < 12 ) return nif->inherits( index, "NiAVObject" ); // Not Skyrim - // Skyrim - return nif->inherits( index, "NiGeometry" ); + // Skyrim and later + return nif->inherits( index, "NiGeometry" ) || nif->inherits( index, "BSTriShape" ); } QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final @@ -274,6 +272,12 @@ class spAttachProperty final : public Spell QStringList ids = nif->allNiBlocks(); ids.sort(); for ( const QString& id : ids ) { + if ( (nif->inherits( index, "NiGeometry" ) || nif->inherits( index, "BSTriShape" )) + && nif->getUserVersion2() > 34 ) { + if ( !(id == "BSLightingShaderProperty" || id == "BSEffectShaderProperty" || id == "NiAlphaProperty") ) + continue; + } + if ( nif->inherits( id, "NiProperty" ) ) menu.addAction( id ); } @@ -288,9 +292,16 @@ class spAttachProperty final : public Spell QModelIndex iProperty = nif->insertNiBlock( act->text(), nif->getBlockNumber( index ) + 1 ); if ( !addLink( nif, iParent, "Properties", nif->getBlockNumber( iProperty ) ) ) { - // try Skyrim - if ( !addLink( nif, iParent, "BS Properties", nif->getBlockNumber( iProperty ) ) ) { - qCWarning( nsSpell ) << Spell::tr( "failed to attach property block; perhaps the array is full?" ); + // Skyrim and later + auto name = nif->getBlockName( iProperty ); + if ( name == "BSLightingShaderProperty" || name == "BSEffectShaderProperty" ) { + if ( !nif->setLink( iParent, "Shader Property", nif->getBlockNumber( iProperty ) ) ) { + qCWarning( nsSpell ) << Spell::tr( "Failed to attach property." ); + } + } else if ( name == "NiAlphaProperty" ) { + if ( !nif->setLink( iParent, "Alpha Property", nif->getBlockNumber( iProperty ) ) ) { + qCWarning( nsSpell ) << Spell::tr( "Failed to attach property." ); + } } } diff --git a/src/spells/havok.cpp b/src/spells/havok.cpp index c9d8bc5d0..ed83eb0f2 100644 --- a/src/spells/havok.cpp +++ b/src/spells/havok.cpp @@ -222,7 +222,12 @@ class spConstraintHelper final : public Spell { return nif && nif->isNiBlock( nif->getBlock( index ), - { "bhkMalleableConstraint", "bhkRagdollConstraint", "bhkLimitedHingeConstraint", "bhkHingeConstraint", "bhkPrismaticConstraint" } + { "bhkMalleableConstraint", + "bhkBreakableConstraint", + "bhkRagdollConstraint", + "bhkLimitedHingeConstraint", + "bhkHingeConstraint", + "bhkPrismaticConstraint" } ); } @@ -231,7 +236,7 @@ class spConstraintHelper final : public Spell QModelIndex iConstraint = nif->getBlock( index ); QString name = nif->itemName( iConstraint ); - if ( name == "bhkMalleableConstraint" ) { + if ( name == "bhkMalleableConstraint" || name == "bhkBreakableConstraint" ) { if ( nif->getIndex( iConstraint, "Ragdoll" ).isValid() ) { name = "bhkRagdollConstraint"; } else if ( nif->getIndex( iConstraint, "Limited Hinge" ).isValid() ) { @@ -252,29 +257,37 @@ class spConstraintHelper final : public Spell Transform transA = bodyTrans( nif, iBodyA ); Transform transB = bodyTrans( nif, iBodyB ); + QModelIndex iConstraintData; if ( name == "bhkLimitedHingeConstraint" ) { - iConstraint = nif->getIndex( iConstraint, "Limited Hinge" ); + iConstraintData = nif->getIndex( iConstraint, "Limited Hinge" ); + if ( !iConstraintData.isValid() ) + iConstraintData = iConstraint; } else if ( name == "bhkRagdollConstraint" ) { - iConstraint = nif->getIndex( iConstraint, "Ragdoll" ); + iConstraintData = nif->getIndex( iConstraint, "Ragdoll" ); + if ( !iConstraintData.isValid() ) + iConstraintData = iConstraint; } else if ( name == "bhkHingeConstraint" ) { - iConstraint = nif->getIndex( iConstraint, "Hinge" ); + iConstraintData = nif->getIndex( iConstraint, "Hinge" ); + if ( !iConstraintData.isValid() ) + iConstraintData = iConstraint; } - if ( !iConstraint.isValid() ) + if ( !iConstraintData.isValid() ) return index; - Vector3 pivot = Vector3( nif->get( iConstraint, "Pivot A" ) ) * havokConst; + Vector3 pivot = Vector3( nif->get( iConstraintData, "Pivot A" ) ) * havokConst; pivot = transA * pivot; pivot = transB.rotation.inverted() * ( pivot - transB.translation ) / transB.scale / havokConst; - nif->set( iConstraint, "Pivot B", { pivot[0], pivot[1], pivot[2], 0 } ); + nif->set( iConstraintData, "Pivot B", { pivot[0], pivot[1], pivot[2], 0 } ); - // TODO: bhkHingeConstraint - QString axleA, axleB, twistA, twistB; - if ( name == "bhkLimitedHingeConstraint" ) { + QString axleA, axleB, twistA, twistB, twistA2, twistB2; + if ( name.endsWith( "HingeConstraint" ) ) { axleA = "Axle A"; axleB = "Axle B"; - twistA = "Perp2 Axle In A2"; - twistB = "Perp2 Axle In B2"; + twistA = "Perp2 Axle In A1"; + twistB = "Perp2 Axle In B1"; + twistA2 = "Perp2 Axle In A2"; + twistB2 = "Perp2 Axle In B2"; } else if ( name == "bhkRagdollConstraint" ) { axleA = "Plane A"; axleB = "Plane B"; @@ -285,15 +298,22 @@ class spConstraintHelper final : public Spell if ( axleA.isEmpty() || axleB.isEmpty() || twistA.isEmpty() || twistB.isEmpty() ) return index; - Vector3 axle = Vector3( nif->get( iConstraint, axleA ) ); + Vector3 axle = Vector3( nif->get( iConstraintData, axleA ) ); axle = transA.rotation * axle; axle = transB.rotation.inverted() * axle; - nif->set( iConstraint, axleB, { axle[0], axle[1], axle[2], 0 } ); + nif->set( iConstraintData, axleB, { axle[0], axle[1], axle[2], 0 } ); - axle = Vector3( nif->get( iConstraint, twistA ) ); + axle = Vector3( nif->get( iConstraintData, twistA ) ); axle = transA.rotation * axle; axle = transB.rotation.inverted() * axle; - nif->set( iConstraint, twistB, { axle[0], axle[1], axle[2], 0 } ); + nif->set( iConstraintData, twistB, { axle[0], axle[1], axle[2], 0 } ); + + if ( !twistA2.isEmpty() && !twistB2.isEmpty() ) { + axle = Vector3( nif->get( iConstraintData, twistA2 ) ); + axle = transA.rotation * axle; + axle = transB.rotation.inverted() * axle; + nif->set( iConstraintData, twistB2, { axle[0], axle[1], axle[2], 0 } ); + } return index; } diff --git a/src/xml/nifexpr.cpp b/src/xml/nifexpr.cpp index a0bcec61b..97165795f 100644 --- a/src/xml/nifexpr.cpp +++ b/src/xml/nifexpr.cpp @@ -214,6 +214,8 @@ void NifExpr::partition( const QString & cond, int offset /*= 0*/ ) lhs.setValue( cond ); if ( reUInt.match( cond ).hasMatch() ) { + bool ok = false; + lhs.setValue( cond.toUInt( &ok, 16 ) ); lhs.convert( QVariant::UInt ); } else if ( reInt.match( cond ).hasMatch() ) { lhs.convert( QVariant::Int ); From d940f0a358c39c41be0327126c9690c9e6cecff0 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Fri, 27 Oct 2017 22:13:30 -0400 Subject: [PATCH 088/152] [KFM] Fix KFM loading KfmModel and KfmXml were neglected after changes to NifData and BaseModel. Also, saving a KFM would always error 100% of the time since pre-1.1.x, even though it saves correctly because of a typo. --- src/model/kfmmodel.cpp | 61 +++++++++++++++++++++++++++++++----------- src/nifskope.cpp | 2 ++ src/xml/kfmxml.cpp | 37 ++++++++++++++++++------- 3 files changed, 75 insertions(+), 25 deletions(-) diff --git a/src/model/kfmmodel.cpp b/src/model/kfmmodel.cpp index 6140af34c..9029375cc 100644 --- a/src/model/kfmmodel.cpp +++ b/src/model/kfmmodel.cpp @@ -86,12 +86,29 @@ quint32 KfmModel::version2number( const QString & s ) bool KfmModel::evalVersion( NifItem * item, bool chkParents ) const { - if ( item == root ) + if ( item->isVercondValid() ) + return item->versionCondition(); + + item->setVersionCondition( item == root ); + if ( item->versionCondition() ) return true; - if ( chkParents && item->parent() ) - if ( !evalVersion( item->parent(), true ) ) + if ( chkParents && item->parent() ) { + // Set false if parent is false and early reject + item->setVersionCondition( evalVersion( item->parent(), true ) ); + if ( !item->versionCondition() ) return false; + } + + // Early reject for ver1/ver2 + item->setVersionCondition( item->evalVersion( version ) ); + if ( !item->versionCondition() ) + return false; + + // Early reject for vercond + item->setVersionCondition( item->vercond().isEmpty() ); + if ( item->versionCondition() ) + return true; return item->evalVersion( version ); } @@ -103,8 +120,12 @@ void KfmModel::clear() filename = QString(); folder = QString(); root->killChildren(); - insertType( root, NifData( "Kfm", "Kfm" ) ); + auto rootData = NifData( "Kfm", "Kfm" ); + rootData.setIsCompound( true ); + rootData.setIsConditionless( true ); + insertType( root, rootData ); kfmroot = root->child( 0 ); + kfmroot->setCondition( true ); version = 0x0200000b; endResetModel(); @@ -129,7 +150,7 @@ static QString parentPrefix( const QString & x ) bool KfmModel::updateArrayItem( NifItem * array ) { - if ( array->arr1().isEmpty() ) + if ( !array->isArray() ) return false; int d1 = getArraySize( array ); @@ -147,7 +168,17 @@ bool KfmModel::updateArrayItem( NifItem * array ) int rows = array->childCount(); if ( d1 > rows ) { - NifData data( array->name(), array->type(), array->temp(), NifValue( NifValue::type( array->type() ) ), parentPrefix( array->arg() ), parentPrefix( array->arr2() ), QString(), QString(), 0, 0 ); + NifData data( array->name(), + array->type(), + array->temp(), + NifValue( NifValue::type( array->type() ) ), + parentPrefix( array->arg() ), + parentPrefix( array->arr2() ) ); + + // Fill data flags + data.setIsConditionless( true ); + data.setIsCompound( array->isCompound() ); + data.setIsArray( array->isMultiArray() ); beginInsertRows( createIndex( array->row(), 0, array ), rows, d1 - 1 ); @@ -176,18 +207,15 @@ bool KfmModel::updateArrayItem( NifItem * array ) void KfmModel::insertType( NifItem * parent, const NifData & data, int at ) { - if ( !data.arr1().isEmpty() ) { + if ( data.isArray() ) { NifItem * array = insertBranch( parent, data, at ); if ( evalCondition( array ) ) updateArrayItem( array ); - return; - } - - NifBlockPtr compound = compounds.value( data.type() ); + } else if ( data.isCompound() ) { - if ( compound ) { + NifBlockPtr compound = compounds.value( data.type() ); NifItem * branch = insertBranch( parent, data, at ); branch->prepareInsert( compound->types.count() ); @@ -204,7 +232,7 @@ void KfmModel::insertType( NifItem * parent, const NifData & data, int at ) } } - foreach ( NifData d, compound->types ) { + for ( NifData & d : compound->types ) { if ( d.type() == "TEMPLATE" ) { d.setType( tmp ); d.value.changeType( NifValue::type( tmp ) ); @@ -287,7 +315,7 @@ bool KfmModel::save( QIODevice & device ) const { NifOStream stream( this, &device ); - if ( !kfmroot || save( kfmroot, stream ) ) { + if ( !kfmroot || !save( kfmroot, stream ) ) { Message::critical( nullptr, tr( "Failed to write KFM file." ) ); return false; } @@ -303,8 +331,11 @@ bool KfmModel::load( NifItem * parent, NifIStream & stream ) for ( int row = 0; row < parent->childCount(); row++ ) { NifItem * child = parent->child( row ); + if ( !child->isConditionless() ) + child->invalidateCondition(); + if ( evalCondition( child ) ) { - if ( !child->arr1().isEmpty() ) { + if ( child->isArray() ) { if ( !updateArrayItem( child ) ) return false; diff --git a/src/nifskope.cpp b/src/nifskope.cpp index f4bd893a6..abc7ea25b 100644 --- a/src/nifskope.cpp +++ b/src/nifskope.cpp @@ -975,6 +975,8 @@ void NifSkope::load() emit completeLoading( kfm->loadFromFile( fname ), fname ); f.setFile( kfm->getFolder(), kfm->get( kfm->getKFMroot(), "NIF File Name" ) ); + + return; } bool loaded = nif->loadFromFile( fname ); diff --git a/src/xml/kfmxml.cpp b/src/xml/kfmxml.cpp index 388c63f3b..878fd0e0e 100644 --- a/src/xml/kfmxml.cpp +++ b/src/xml/kfmxml.cpp @@ -130,21 +130,38 @@ class KfmXmlHandler final : public QXmlDefaultHandler case 2: if ( x == 3 ) { + QString type = list.value( "type" ); + QString tmpl = list.value( "template" ); + QString arr1 = list.value( "arr1" ); + QString arr2 = list.value( "arr2" ); + QString cond = list.value( "cond" ); + QString ver1 = list.value( "ver1" ); + QString ver2 = list.value( "ver2" ); + QString abs = list.value( "abstract" ); + NifData data( list.value( "name" ), - list.value( "type" ), - list.value( "template" ), - NifValue( NifValue::type( list.value( "type" ) ) ), + type, + tmpl, + NifValue( NifValue::type( type ) ), list.value( "arg" ), - list.value( "arr1" ), - list.value( "arr2" ), - list.value( "cond" ), - KfmModel::version2number( list.value( "ver1" ) ), - KfmModel::version2number( list.value( "ver2" ) ) + arr1, + arr2, + cond, + KfmModel::version2number( ver1 ), + KfmModel::version2number( ver2 ) ); - if ( list.value( "abstract" ) == "1" ) - data.setAbstract( true ); + bool isTemplated = (type == "TEMPLATE" || tmpl == "TEMPLATE"); + bool isCompound = KfmModel::compounds.contains( type ); + bool isArray = !arr1.isEmpty(); + bool isMultiArray = !arr2.isEmpty(); + + data.setAbstract( abs == "1" ); + data.setTemplated( isTemplated ); + data.setIsCompound( isCompound ); + data.setIsArray( isArray ); + data.setIsMultiArray( isMultiArray ); if ( data.name().isEmpty() || data.type().isEmpty() ) err( tr( "add needs at least name and type attributes" ) ); From 2f524d35df415870c8ee7a54dfeac73c1eea1c45 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 31 Oct 2017 04:16:48 -0400 Subject: [PATCH 089/152] [Spells] Fix strippify for large shapes NvTriStrip does not inherently limit points per strip at 65535 which is necessary for the NIF format. Also the data type used signed short which automatically caused more than 32768 tris to crash the program. Made spDuplicateBranch accessible in blocks.h for this. Changed NvTriStrip to split before 65535 points per strip regardless of stitch boolean. Changed strippify spell to move any excess tris/points into their own NiTriStrips. --- lib/NvTriStrip/NvTriStrip.cpp | 4 +- lib/NvTriStrip/NvTriStripObjects.cpp | 20 ++- lib/NvTriStrip/NvTriStripObjects.h | 2 +- src/spells/blocks.cpp | 199 +++++++++++++-------------- src/spells/blocks.h | 12 ++ src/spells/strippify.cpp | 71 +++++++++- 6 files changed, 193 insertions(+), 115 deletions(-) diff --git a/lib/NvTriStrip/NvTriStrip.cpp b/lib/NvTriStrip/NvTriStrip.cpp index 74e858679..7f0fb9c50 100644 --- a/lib/NvTriStrip/NvTriStrip.cpp +++ b/lib/NvTriStrip/NvTriStrip.cpp @@ -290,7 +290,7 @@ bool GenerateStrips(const unsigned short* in_indices, const unsigned int in_numI stripifier.CreateStrips(tempStrips, stripIndices, bStitchStrips, numSeparateStrips, bRestart, restartVal); //if we're stitching strips together, we better get back only one strip from CreateStrips() - assert( (bStitchStrips && (numSeparateStrips == 1)) || !bStitchStrips); + assert( (bStitchStrips && (numSeparateStrips == 1)) || !bStitchStrips || stripIndices.size() > USHRT_MAX ); //convert to output format *numGroups = numSeparateStrips; //for the strips @@ -306,7 +306,7 @@ bool GenerateStrips(const unsigned short* in_indices, const unsigned int in_numI { int stripLength = 0; - if(!bStitchStrips) + if(!bStitchStrips || stripIndices.size() > USHRT_MAX) { //if we've got multiple strips, we need to figure out the correct length size_t i; diff --git a/lib/NvTriStrip/NvTriStripObjects.cpp b/lib/NvTriStrip/NvTriStripObjects.cpp index d0fafa551..7f5bd1774 100644 --- a/lib/NvTriStrip/NvTriStripObjects.cpp +++ b/lib/NvTriStrip/NvTriStripObjects.cpp @@ -971,11 +971,17 @@ void NvStripifier::CreateStrips(const NvStripInfoVec& allStrips, IntVec& stripIn int nStripCount = allStrips.size(); assert(nStripCount > 0); + // Split into two strip lengths > USHRT_MAX + bool split = false; + //we infer the cw/ccw ordering depending on the number of indices //this is screwed up by the fact that we insert -1s to denote changing strips //this is to account for that int accountForNegatives = 0; + if ( bStitchStrips || bRestart ) + numSeparateStrips = 1; + for (int i = 0; i < nStripCount; i++) { NvStripInfo *strip = allStrips[i]; @@ -1072,6 +1078,17 @@ void NvStripifier::CreateStrips(const NvStripInfoVec& allStrips, IntVec& stripIn } } + if ( i < nStripCount - 1 ) { + // Prevent points per strip of over 65535 + NvStripInfo * stripNext = allStrips[i + 1]; + if ( stripNext && !split && (stripIndices.size() + stripNext->m_faces.size() * 4) >= USHRT_MAX ) { + stripIndices.push_back( -1 ); + accountForNegatives++; + numSeparateStrips++; + split = true; + } + } + // Double tap between strips. if (bStitchStrips && !bRestart) { @@ -1095,9 +1112,6 @@ void NvStripifier::CreateStrips(const NvStripInfoVec& allStrips, IntVec& stripIn tLastFace.m_v1 = tLastFace.m_v2; tLastFace.m_v2 = tLastFace.m_v2; } - - if(bStitchStrips || bRestart) - numSeparateStrips = 1; } diff --git a/lib/NvTriStrip/NvTriStripObjects.h b/lib/NvTriStrip/NvTriStripObjects.h index 7b2d82295..5e60cd8b7 100644 --- a/lib/NvTriStrip/NvTriStripObjects.h +++ b/lib/NvTriStrip/NvTriStripObjects.h @@ -102,7 +102,7 @@ typedef std::list NvFaceInfoList; typedef std::list NvStripList; typedef std::vector NvEdgeInfoVec; -typedef std::vector WordVec; +typedef std::vector WordVec; typedef std::vector IntVec; typedef std::vector MyVertexVec; typedef std::vector MyFaceVec; diff --git a/src/spells/blocks.cpp b/src/spells/blocks.cpp index 6dd8ef8ee..90e612668 100644 --- a/src/spells/blocks.cpp +++ b/src/spells/blocks.cpp @@ -1191,138 +1191,131 @@ class spDuplicateBlock final : public Spell REGISTER_SPELL( spDuplicateBlock ) //! Duplicate a branch in place -class spDuplicateBranch final : public Spell -{ -public: - QString name() const override final { return Spell::tr( "Duplicate Branch" ); } - QString page() const override final { return Spell::tr( "Block" ); } - QKeySequence hotkey() const { return{ Qt::CTRL + Qt::Key_D }; } - bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final - { - return nif->isNiBlock( index ); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final - { - // from spCopyBranch - QList blocks; - populateBlocks( blocks, nif, nif->getBlockNumber( index ) ); - - QMap blockMap; +bool spDuplicateBranch::isApplicable( const NifModel * nif, const QModelIndex & index ) +{ + return nif->isNiBlock( index ); +} - for ( int b = 0; b < blocks.count(); b++ ) - blockMap.insert( blocks[b], b ); +QModelIndex spDuplicateBranch::cast( NifModel * nif, const QModelIndex & index ) +{ + // from spCopyBranch + QList blocks; + populateBlocks( blocks, nif, nif->getBlockNumber( index ) ); - QMap parentMap; - for ( const auto block : blocks ) - { - for ( const auto link : nif->getParentLinks( block ) ) { - if ( !blocks.contains( link ) && !parentMap.contains( link ) ) { - QString failMessage = Spell::tr( "parent link invalid" ); - QModelIndex iParent = nif->getBlock( link ); + QMap blockMap; - if ( iParent.isValid() ) { - failMessage = Spell::tr( "parent unnamed" ); - QString name = nif->get( iParent, "Name" ); + for ( int b = 0; b < blocks.count(); b++ ) + blockMap.insert( blocks[b], b ); - if ( !name.isEmpty() ) { - parentMap.insert( link, nif->itemName( iParent ) + "|" + name ); - continue; - } + QMap parentMap; + for ( const auto block : blocks ) + { + for ( const auto link : nif->getParentLinks( block ) ) { + if ( !blocks.contains( link ) && !parentMap.contains( link ) ) { + QString failMessage = Spell::tr( "parent link invalid" ); + QModelIndex iParent = nif->getBlock( link ); + + if ( iParent.isValid() ) { + failMessage = Spell::tr( "parent unnamed" ); + QString name = nif->get( iParent, "Name" ); + + if ( !name.isEmpty() ) { + parentMap.insert( link, nif->itemName( iParent ) + "|" + name ); + continue; } - - Message::critical( nullptr, Spell::tr( "%1 failed with errors." ).arg( name() ), Spell::tr( "failed to map parent link %1 %2 for block %3 %4; %5." ) - .arg( link ) - .arg( nif->itemName( nif->getBlock( link ) ) ) - .arg( block ) - .arg( nif->itemName( nif->getBlock( block ) ) ) - .arg( failMessage ) - ); - return index; } + + Message::critical( nullptr, Spell::tr( "%1 failed with errors." ).arg( name() ), Spell::tr( "failed to map parent link %1 %2 for block %3 %4; %5." ) + .arg( link ) + .arg( nif->itemName( nif->getBlock( link ) ) ) + .arg( block ) + .arg( nif->itemName( nif->getBlock( block ) ) ) + .arg( failMessage ) + ); + return index; } } + } - QByteArray data; - QBuffer buffer( &data ); - - if ( buffer.open( QIODevice::WriteOnly ) ) { - QDataStream ds( &buffer ); - ds << blocks.count(); - ds << blockMap; - ds << parentMap; - for ( const auto block : blocks ) { - ds << nif->itemName( nif->getBlock( block ) ); - - if ( !nif->saveIndex( buffer, nif->getBlock( block ) ) ) { - Message::critical( nullptr, Spell::tr( "%1 failed with errors." ).arg( name() ), Spell::tr( "failed to save block %1 %2." ) - .arg( block ) - .arg( nif->itemName( nif->getBlock( block ) ) ) - ); - return index; - } + QByteArray data; + QBuffer buffer( &data ); + + if ( buffer.open( QIODevice::WriteOnly ) ) { + QDataStream ds( &buffer ); + ds << blocks.count(); + ds << blockMap; + ds << parentMap; + for ( const auto block : blocks ) { + ds << nif->itemName( nif->getBlock( block ) ); + + if ( !nif->saveIndex( buffer, nif->getBlock( block ) ) ) { + Message::critical( nullptr, Spell::tr( "%1 failed with errors." ).arg( name() ), Spell::tr( "failed to save block %1 %2." ) + .arg( block ) + .arg( nif->itemName( nif->getBlock( block ) ) ) + ); + return index; } } + } - // from spPasteBranch - if ( buffer.open( QIODevice::ReadOnly ) ) { - QDataStream ds( &buffer ); + // from spPasteBranch + if ( buffer.open( QIODevice::ReadOnly ) ) { + QDataStream ds( &buffer ); - int count; - ds >> count; + int count; + ds >> count; - QMap blockMap; - ds >> blockMap; - QMutableMapIterator ibm( blockMap ); + QMap blockMap; + ds >> blockMap; + QMutableMapIterator ibm( blockMap ); - while ( ibm.hasNext() ) { - ibm.next(); - ibm.value() += nif->getBlockCount(); - } + while ( ibm.hasNext() ) { + ibm.next(); + ibm.value() += nif->getBlockCount(); + } - QMap parentMap; - ds >> parentMap; + QMap parentMap; + ds >> parentMap; - QMapIterator ipm( parentMap ); + QMapIterator ipm( parentMap ); - while ( ipm.hasNext() ) { - ipm.next(); - qint32 block = getBlockByName( nif, ipm.value() ); + while ( ipm.hasNext() ) { + ipm.next(); + qint32 block = getBlockByName( nif, ipm.value() ); - if ( block >= 0 ) { - blockMap.insert( ipm.key(), block ); - } else { - Message::critical( nullptr, Spell::tr( "%1 failed with errors." ).arg( name() ), Spell::tr( "failed to map parent link %1" ) - .arg( ipm.value() ) - ); - return index; - } + if ( block >= 0 ) { + blockMap.insert( ipm.key(), block ); + } else { + Message::critical( nullptr, Spell::tr( "%1 failed with errors." ).arg( name() ), Spell::tr( "failed to map parent link %1" ) + .arg( ipm.value() ) + ); + return index; } + } - QModelIndex iRoot; - - for ( int c = 0; c < count; c++ ) { - QString type; - ds >> type; + QModelIndex iRoot; - QModelIndex block = nif->insertNiBlock( type, -1 ); + for ( int c = 0; c < count; c++ ) { + QString type; + ds >> type; - if ( !nif->loadAndMapLinks( buffer, block, blockMap ) ) - return index; + QModelIndex block = nif->insertNiBlock( type, -1 ); - if ( c == 0 ) - iRoot = block; - } + if ( !nif->loadAndMapLinks( buffer, block, blockMap ) ) + return index; - blockLink( nif, nif->getBlock( nif->getParent( nif->getBlockNumber( index ) ) ), iRoot ); - - return iRoot; + if ( c == 0 ) + iRoot = block; } - return QModelIndex(); + blockLink( nif, nif->getBlock( nif->getParent( nif->getBlockNumber( index ) ) ), iRoot ); + + return iRoot; } -}; + + return QModelIndex(); +} REGISTER_SPELL( spDuplicateBranch ) diff --git a/src/spells/blocks.h b/src/spells/blocks.h index db55a5f38..88b24afa9 100644 --- a/src/spells/blocks.h +++ b/src/spells/blocks.h @@ -11,6 +11,18 @@ * All classes here inherit from the Spell class. */ +class spDuplicateBranch final : public Spell +{ +public: + QString name() const override final { return Spell::tr( "Duplicate Branch" ); } + QString page() const override final { return Spell::tr( "Block" ); } + QKeySequence hotkey() const { return{ Qt::CTRL + Qt::Key_D }; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final; + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final; +}; + //! Remove a branch (a block and its descendents) class spRemoveBranch final : public Spell { diff --git a/src/spells/strippify.cpp b/src/spells/strippify.cpp index 40f750863..e98a7da6d 100644 --- a/src/spells/strippify.cpp +++ b/src/spells/strippify.cpp @@ -1,5 +1,7 @@ #include "spellbook.h" +#include "blocks.h" + #include "lib/nvtristripwrapper.h" @@ -61,13 +63,26 @@ class spStrippify final : public Spell //qDebug() << "num triangles" << triangles.count() << "skipped" << skip; - QList > strips = stripify( triangles ); + QList > strips = stripify( triangles, true ); if ( strips.count() <= 0 ) return idx; - nif->insertNiBlock( "NiTriStripsData", nif->getBlockNumber( idx ) + 1 ); - QModelIndex iStripData = nif->getBlock( nif->getBlockNumber( idx ) + 1, "NiTriStripsData" ); + uint numTriangles = 0; + for ( const QVector& strip : strips ) { + numTriangles += strip.count() - 2; + } + + if ( numTriangles > USHRT_MAX * 2 ) { + Message::append( tr( "Strippify failed on one or more blocks." ), + tr( "Block %1: Too many triangles (%2) to strippify this shape." ) + .arg( nif->getBlockNumber( idx ) ) + .arg( numTriangles ) + ); + return idx; + } + + QModelIndex iStripData = nif->insertNiBlock( "NiTriStripsData", nif->getBlockNumber( idx ) + 1 ); if ( iStripData.isValid() ) { copyValue( nif, iStripData, iData, "Num Vertices" ); @@ -125,16 +140,14 @@ class spStrippify final : public Spell nif->updateArray( iLengths ); nif->updateArray( iPoints ); int x = 0; - int z = 0; for ( const QVector& strip : strips ) { nif->set( iLengths.child( x, 0 ), strip.count() ); QModelIndex iStrip = iPoints.child( x, 0 ); nif->updateArray( iStrip ); nif->setArray( iStrip, strip ); x++; - z += strip.count() - 2; } - nif->set( iStripData, "Num Triangles", z ); + nif->set( iStripData, "Num Triangles", numTriangles ); nif->setData( idx.sibling( idx.row(), NifModel::NameCol ), "NiTriStrips" ); int lnk = nif->getLink( idx, "Data" ); @@ -143,6 +156,52 @@ class spStrippify final : public Spell } } + // Move the triangles over 65535 into their own shape by + // splitting the two strips between two NiTriStrips + if ( numTriangles > USHRT_MAX ) { + spDuplicateBranch dupe; + + // Copy the entire NiTriStrips branch + auto iStrip2 = dupe.cast( nif, idx ); + auto iStrip2Data = nif->getBlock( nif->getLink( iStrip2, "Data" ), "NiTriStripsData" ); + if ( !iStrip2Data.isValid() || strips.count() != 2 ) + return QModelIndex(); + + // Update Original Shape + nif->set( iStripData, "Num Strips", 1 ); + nif->set( iStripData, "Has Points", 1 ); + + QModelIndex iLengths = nif->getIndex( iStripData, "Strip Lengths" ); + QModelIndex iPoints = nif->getIndex( iStripData, "Points" ); + + auto stripsA = strips.at(0); + if ( iLengths.isValid() && iPoints.isValid() ) { + nif->updateArray( iLengths ); + nif->set( iLengths.child( 0, 0 ), stripsA.count() ); + nif->updateArray( iPoints ); + nif->updateArray( iPoints.child( 0, 0 ) ); + nif->setArray( iPoints.child( 0, 0 ), stripsA ); + nif->set( iStripData, "Num Triangles", stripsA.count() - 2 ); + } + + // Update New Shape + nif->set( iStrip2Data, "Num Strips", 1 ); + nif->set( iStrip2Data, "Has Points", 1 ); + + iLengths = nif->getIndex( iStrip2Data, "Strip Lengths" ); + iPoints = nif->getIndex( iStrip2Data, "Points" ); + + auto stripsB = strips.at(1); + if ( iLengths.isValid() && iPoints.isValid() ) { + nif->updateArray( iLengths ); + nif->set( iLengths.child( 0, 0 ), stripsB.count() ); + nif->updateArray( iPoints ); + nif->updateArray( iPoints.child( 0, 0 ) ); + nif->setArray( iPoints.child( 0, 0 ), stripsB ); + nif->set( iStrip2Data, "Num Triangles", stripsB.count() - 2 ); + } + } + return idx; } }; From 08950884687fd58957c32a29ec9bac7fe31b6c5f Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Wed, 8 Nov 2017 13:49:42 -0500 Subject: [PATCH 090/152] [Spells] Expose Copy/Paste Branch and Reorder Blocks publicly Necessary to use from other spells. Also exposed addLink/delLink. --- NifSkope.pro | 1 + src/spells/blocks.cpp | 311 +++++++++++++++++++--------------------- src/spells/blocks.h | 47 ++++++ src/spells/sanitize.cpp | 160 ++++++++++----------- src/spells/sanitize.h | 27 ++++ 5 files changed, 302 insertions(+), 244 deletions(-) create mode 100644 src/spells/sanitize.h diff --git a/NifSkope.pro b/NifSkope.pro index e225c4ceb..b3126001b 100644 --- a/NifSkope.pro +++ b/NifSkope.pro @@ -179,6 +179,7 @@ HEADERS += \ src/spells/blocks.h \ src/spells/mesh.h \ src/spells/misc.h \ + src/spells/sanitize.h \ src/spells/skeleton.h \ src/spells/stringpalette.h \ src/spells/tangentspace.h \ diff --git a/src/spells/blocks.cpp b/src/spells/blocks.cpp index 90e612668..cf9d0d7ec 100644 --- a/src/spells/blocks.cpp +++ b/src/spells/blocks.cpp @@ -28,7 +28,7 @@ * @param array The name of the link array * @param link A reference to the block to insert into the link array */ -static bool addLink( NifModel * nif, const QModelIndex & iParent, const QString & array, int link ) +bool addLink( NifModel * nif, const QModelIndex & iParent, const QString & array, int link ) { QModelIndex iSize = nif->getIndex( iParent, QString( "Num %1" ).arg( array ) ); QModelIndex iArray = nif->getIndex( iParent, array ); @@ -67,7 +67,7 @@ static bool addLink( NifModel * nif, const QModelIndex & iParent, const QString * @param array The name of the link array * @param link A reference to the block to remove from the link array */ -static void delLink( NifModel * nif, const QModelIndex & iParent, QString array, int link ) +void delLink( NifModel * nif, const QModelIndex & iParent, QString array, int link ) { QModelIndex iSize = nif->getIndex( iParent, QString( "Num %1" ).arg( array ) ); QModelIndex iArray = nif->getIndex( iParent, array ); @@ -81,9 +81,13 @@ static void delLink( NifModel * nif, const QModelIndex & iParent, QString array, } } -// documented in blocks.h -// XXX at the moment, we don't care if this fails or not... -// XXX probably should return a bool? + +//! Link one block to another +/*! +* @param nif The model +* @param index The block to link to (becomes parent) +* @param iBlock The block to link (becomes child) +*/ void blockLink( NifModel * nif, const QModelIndex & index, const QModelIndex & iBlock ) { if ( nif->isLink( index ) && nif->inherits( iBlock, nif->itemTmplt( index ) ) ) { @@ -624,206 +628,193 @@ class spPasteOverBlock final : public Spell REGISTER_SPELL( spPasteOverBlock ) //! Copy a branch (a block and its descendents) to the clipboard -class spCopyBranch final : public Spell + +bool spCopyBranch::isApplicable( const NifModel * nif, const QModelIndex & index ) { -public: - QString name() const override final { return Spell::tr( "Copy Branch" ); } - QString page() const override final { return Spell::tr( "Block" ); } - QKeySequence hotkey() const { return QKeySequence( QKeySequence::Copy ); } + return nif->isNiBlock( index ); +} - bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final - { - return nif->isNiBlock( index ); - } +QModelIndex spCopyBranch::cast( NifModel * nif, const QModelIndex & index ) +{ + QList blocks; + populateBlocks( blocks, nif, nif->getBlockNumber( index ) ); - QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final - { - QList blocks; - populateBlocks( blocks, nif, nif->getBlockNumber( index ) ); + QMap blockMap; - QMap blockMap; + for ( int b = 0; b < blocks.count(); b++ ) + blockMap.insert( blocks[b], b ); - for ( int b = 0; b < blocks.count(); b++ ) - blockMap.insert( blocks[b], b ); + QMap parentMap; + for ( const auto block : blocks ) + { + for ( const auto link : nif->getParentLinks( block ) ) { + if ( !blocks.contains( link ) && !parentMap.contains( link ) ) { + QString failMessage = Spell::tr( "parent link invalid" ); + QModelIndex iParent = nif->getBlock( link ); - QMap parentMap; - for ( const auto block : blocks ) - { - for ( const auto link : nif->getParentLinks( block ) ) { - if ( !blocks.contains( link ) && !parentMap.contains( link ) ) { - QString failMessage = Spell::tr( "parent link invalid" ); - QModelIndex iParent = nif->getBlock( link ); - - if ( iParent.isValid() ) { - failMessage = Spell::tr( "parent unnamed" ); - QString name = nif->get( iParent, "Name" ); - - if ( !name.isEmpty() ) { - parentMap.insert( link, nif->itemName( iParent ) + "|" + name ); - continue; - } - } + if ( iParent.isValid() ) { + failMessage = Spell::tr( "parent unnamed" ); + QString name = nif->get( iParent, "Name" ); - Message::critical( nullptr, Spell::tr( "%1 failed with errors." ).arg( name() ), Spell::tr( "failed to map parent link %1 %2 for block %3 %4; %5." ) - .arg( link ) - .arg( nif->itemName( nif->getBlock( link ) ) ) - .arg( block ) - .arg( nif->itemName( nif->getBlock( block ) ) ) - .arg( failMessage ) - ); - return index; + if ( !name.isEmpty() ) { + parentMap.insert( link, nif->itemName( iParent ) + "|" + name ); + continue; + } } + + Message::critical( nullptr, Spell::tr( "%1 failed with errors." ).arg( name() ), Spell::tr( "failed to map parent link %1 %2 for block %3 %4; %5." ) + .arg( link ) + .arg( nif->itemName( nif->getBlock( link ) ) ) + .arg( block ) + .arg( nif->itemName( nif->getBlock( block ) ) ) + .arg( failMessage ) + ); + return index; } } + } - QByteArray data; - QBuffer buffer( &data ); + QByteArray data; + QBuffer buffer( &data ); - if ( buffer.open( QIODevice::WriteOnly ) ) { - QDataStream ds( &buffer ); - ds << blocks.count(); - ds << blockMap; - ds << parentMap; - for ( const auto block : blocks ) { - ds << nif->itemName( nif->getBlock( block ) ); - - if ( !nif->saveIndex( buffer, nif->getBlock( block ) ) ) { - Message::critical( nullptr, Spell::tr( "%1 failed with errors." ).arg( name() ), Spell::tr( "failed to save block %1 %2." ) - .arg( block ) - .arg( nif->itemName( nif->getBlock( block ) ) ) - ); - return index; - } + if ( buffer.open( QIODevice::WriteOnly ) ) { + QDataStream ds( &buffer ); + ds << blocks.count(); + ds << blockMap; + ds << parentMap; + for ( const auto block : blocks ) { + ds << nif->itemName( nif->getBlock( block ) ); + + if ( !nif->saveIndex( buffer, nif->getBlock( block ) ) ) { + Message::critical( nullptr, Spell::tr( "%1 failed with errors." ).arg( name() ), Spell::tr( "failed to save block %1 %2." ) + .arg( block ) + .arg( nif->itemName( nif->getBlock( block ) ) ) + ); + return index; } - QMimeData * mime = new QMimeData; - mime->setData( QString( "nifskope/nibranch/%1" ).arg( nif->getVersion() ), data ); - QApplication::clipboard()->setMimeData( mime ); } - - return index; + QMimeData * mime = new QMimeData; + mime->setData( QString( "nifskope/nibranch/%1" ).arg( nif->getVersion() ), data ); + QApplication::clipboard()->setMimeData( mime ); } -}; + + return index; +} + REGISTER_SPELL( spCopyBranch ) //! Paste a branch from the clipboard -class spPasteBranch final : public Spell -{ -public: - QString name() const override final { return Spell::tr( "Paste Branch" ); } - QString page() const override final { return Spell::tr( "Block" ); } - // Doesn't work unless the menu entry is unique - QKeySequence hotkey() const { return QKeySequence( QKeySequence::Paste ); } - QString acceptFormat( const QString & format, const NifModel * nif ) - { - Q_UNUSED( nif ); - QStringList split = format.split( "/" ); +QString spPasteBranch::acceptFormat( const QString & format, const NifModel * nif ) +{ + Q_UNUSED( nif ); + QStringList split = format.split( "/" ); - if ( split.value( 0 ) == "nifskope" && split.value( 1 ) == "nibranch" ) - return split.value( 2 ); + if ( split.value( 0 ) == "nifskope" && split.value( 1 ) == "nibranch" ) + return split.value( 2 ); - return QString(); - } + return QString(); +} - bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final - { - if ( index.isValid() && !nif->isNiBlock( index ) && !nif->isLink( index ) ) - return false; +bool spPasteBranch::isApplicable( const NifModel * nif, const QModelIndex & index ) +{ + if ( index.isValid() && !nif->isNiBlock( index ) && !nif->isLink( index ) ) + return false; - const QMimeData * mime = QApplication::clipboard()->mimeData(); + const QMimeData * mime = QApplication::clipboard()->mimeData(); - if ( index.isValid() && mime ) { - for ( const QString& form : mime->formats() ) { - if ( nif->isVersionSupported( nif->version2number( acceptFormat( form, nif ) ) ) ) - return true; - } + if ( index.isValid() && mime ) { + for ( const QString& form : mime->formats() ) { + if ( nif->isVersionSupported( nif->version2number( acceptFormat( form, nif ) ) ) ) + return true; } - - return false; } - QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final - { - const QMimeData * mime = QApplication::clipboard()->mimeData(); - - if ( mime ) { - for ( const QString& form : mime->formats() ) { - QString v = acceptFormat( form, nif ); - - if ( !v.isEmpty() - && ( v == nif->getVersion() - || QMessageBox::question( 0, Spell::tr( "Paste Branch" ), - Spell::tr( "Nif versions differ!

Current File Version: %1
Clipboard Data Version: %2

The results will be unpredictable..." ) - .arg( nif->getVersion() ).arg( v ), Spell::tr( "Continue" ), - Spell::tr( "Cancel" ) - ) == 0 - ) - ) - { - QByteArray data = mime->data( form ); - QBuffer buffer( &data ); - - if ( buffer.open( QIODevice::ReadOnly ) ) { - QDataStream ds( &buffer ); - - int count; - ds >> count; - - QMap blockMap; - ds >> blockMap; - QMutableMapIterator ibm( blockMap ); + return false; +} - while ( ibm.hasNext() ) { - ibm.next(); - ibm.value() += nif->getBlockCount(); - } +QModelIndex spPasteBranch::cast( NifModel * nif, const QModelIndex & index ) +{ + const QMimeData * mime = QApplication::clipboard()->mimeData(); + + if ( mime ) { + for ( const QString& form : mime->formats() ) { + QString v = acceptFormat( form, nif ); + + if ( !v.isEmpty() + && ( v == nif->getVersion() + || QMessageBox::question( 0, Spell::tr( "Paste Branch" ), + Spell::tr( "Nif versions differ!

Current File Version: %1
Clipboard Data Version: %2

The results will be unpredictable..." ) + .arg( nif->getVersion() ).arg( v ), Spell::tr( "Continue" ), + Spell::tr( "Cancel" ) + ) == 0 + ) + ) + { + QByteArray data = mime->data( form ); + QBuffer buffer( &data ); + + if ( buffer.open( QIODevice::ReadOnly ) ) { + QDataStream ds( &buffer ); + + int count; + ds >> count; + + QMap blockMap; + ds >> blockMap; + QMutableMapIterator ibm( blockMap ); + + while ( ibm.hasNext() ) { + ibm.next(); + ibm.value() += nif->getBlockCount(); + } - QMap parentMap; - ds >> parentMap; + QMap parentMap; + ds >> parentMap; - QMapIterator ipm( parentMap ); + QMapIterator ipm( parentMap ); - while ( ipm.hasNext() ) { - ipm.next(); - qint32 block = getBlockByName( nif, ipm.value() ); + while ( ipm.hasNext() ) { + ipm.next(); + qint32 block = getBlockByName( nif, ipm.value() ); - if ( block >= 0 ) { - blockMap.insert( ipm.key(), block ); - } else { - Message::critical( nullptr, Spell::tr( "%1 failed with errors." ).arg( name() ), Spell::tr( "failed to map parent link %1" ) - .arg( ipm.value() ) - ); - return index; - } + if ( block >= 0 ) { + blockMap.insert( ipm.key(), block ); + } else { + Message::critical( nullptr, Spell::tr( "%1 failed with errors." ).arg( name() ), Spell::tr( "failed to map parent link %1" ) + .arg( ipm.value() ) + ); + return index; } + } - QModelIndex iRoot; + QModelIndex iRoot; - for ( int c = 0; c < count; c++ ) { - QString type; - ds >> type; + for ( int c = 0; c < count; c++ ) { + QString type; + ds >> type; - QModelIndex block = nif->insertNiBlock( type, -1 ); + QModelIndex block = nif->insertNiBlock( type, -1 ); - if ( !nif->loadAndMapLinks( buffer, block, blockMap ) ) - return index; + if ( !nif->loadAndMapLinks( buffer, block, blockMap ) ) + return index; - if ( c == 0 ) - iRoot = block; - } + if ( c == 0 ) + iRoot = block; + } - blockLink( nif, index, iRoot ); + blockLink( nif, index, iRoot ); - return iRoot; - } + return iRoot; } } } - - return QModelIndex(); } -}; + + return QModelIndex(); +} + REGISTER_SPELL( spPasteBranch ) diff --git a/src/spells/blocks.h b/src/spells/blocks.h index 88b24afa9..18031dfa8 100644 --- a/src/spells/blocks.h +++ b/src/spells/blocks.h @@ -11,6 +11,34 @@ * All classes here inherit from the Spell class. */ +//! Copy a branch (a block and its descendents) to the clipboard +class spCopyBranch final : public Spell +{ +public: + QString name() const override final { return Spell::tr( "Copy Branch" ); } + QString page() const override final { return Spell::tr( "Block" ); } + QKeySequence hotkey() const { return QKeySequence( QKeySequence::Copy ); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final; + QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final; +}; + +//! Paste a branch from the clipboard +class spPasteBranch final : public Spell +{ +public: + QString name() const override final { return Spell::tr( "Paste Branch" ); } + QString page() const override final { return Spell::tr( "Block" ); } + // Doesn't work unless the menu entry is unique + QKeySequence hotkey() const { return QKeySequence( QKeySequence::Paste ); } + + QString acceptFormat( const QString & format, const NifModel * nif ); + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final; + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final; +}; + class spDuplicateBranch final : public Spell { public: @@ -35,6 +63,25 @@ class spRemoveBranch final : public Spell QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final; }; + +//! Add a link to the specified block to a link array +/*! +* @param nif The model +* @param iParent The block containing the link array +* @param array The name of the link array +* @param link A reference to the block to insert into the link array +*/ +bool addLink( NifModel * nif, const QModelIndex & iParent, const QString & array, int link ); + +//! Remove a link to a block from the specified link array +/*! +* @param nif The model +* @param iParent The block containing the link array +* @param array The name of the link array +* @param link A reference to the block to remove from the link array +*/ +void delLink( NifModel * nif, const QModelIndex & iParent, QString array, int link ); + //! Link one block to another /*! * @param nif The model diff --git a/src/spells/sanitize.cpp b/src/spells/sanitize.cpp index 1b83dce64..64ede7c9b 100644 --- a/src/spells/sanitize.cpp +++ b/src/spells/sanitize.cpp @@ -1,4 +1,5 @@ #include "spellbook.h" +#include "sanitize.h" #include "spells/misc.h" #include @@ -168,106 +169,97 @@ class spAdjustTextureSources final : public Spell REGISTER_SPELL( spAdjustTextureSources ) //! Reorders blocks -class spSanitizeBlockOrder final : public Spell -{ -public: - QString name() const override final { return Spell::tr( "Reorder Blocks" ); } - QString page() const override final { return Spell::tr( "Sanitize" ); } - // Prevent this from running during auto-sanitize for the time being - // Can really only cause issues with rendering and textureset overrides via the CK - bool sanity() const { return false; } - - bool isApplicable( const NifModel *, const QModelIndex & index ) override final - { - // all files - return !index.isValid(); - } - // check whether the block is of a type that comes before the parent or not - bool childBeforeParent( NifModel * nif, qint32 block ) - { - // get index to the block - QModelIndex iBlock( nif->getBlock( block ) ); - // check its type - return ( - nif->inherits( iBlock, "bhkRefObject" ) - && !nif->inherits( iBlock, "bhkConstraint" ) - ); - } +bool spSanitizeBlockOrder::isApplicable( const NifModel *, const QModelIndex & index ) +{ + // all files + return !index.isValid(); +} - // build the nif tree at node block; the block itself and its children are recursively added to - // the newblocks list - void addTree( NifModel * nif, qint32 block, QList & newblocks ) - { - // is the block already added? - if ( newblocks.contains( block ) ) - return; +// check whether the block is of a type that comes before the parent or not +bool spSanitizeBlockOrder::childBeforeParent( NifModel * nif, qint32 block ) +{ + // get index to the block + QModelIndex iBlock( nif->getBlock( block ) ); + // check its type + return ( + nif->inherits( iBlock, "bhkRefObject" ) + && !nif->inherits( iBlock, "bhkConstraint" ) + ); +} + +// build the nif tree at node block; the block itself and its children are recursively added to +// the newblocks list +void spSanitizeBlockOrder::addTree( NifModel * nif, qint32 block, QList & newblocks ) +{ + // is the block already added? + if ( newblocks.contains( block ) ) + return; - // special case: add bhkConstraint entities before bhkConstraint - // (these are actually links, not refs) - QModelIndex iBlock( nif->getBlock( block ) ); + // special case: add bhkConstraint entities before bhkConstraint + // (these are actually links, not refs) + QModelIndex iBlock( nif->getBlock( block ) ); - if ( nif->inherits( iBlock, "bhkConstraint" ) ) { - for ( const auto entity : nif->getLinkArray( iBlock, "Entities" ) ) { - addTree( nif, entity, newblocks ); - } + if ( nif->inherits( iBlock, "bhkConstraint" ) ) { + for ( const auto entity : nif->getLinkArray( iBlock, "Entities" ) ) { + addTree( nif, entity, newblocks ); } + } - // add all children of block that should be before block - for ( const auto child : nif->getChildLinks( block ) ) { - if ( childBeforeParent( nif, child ) ) - addTree( nif, child, newblocks ); // now add this child and all of its children - } + // add all children of block that should be before block + for ( const auto child : nif->getChildLinks( block ) ) { + if ( childBeforeParent( nif, child ) ) + addTree( nif, child, newblocks ); // now add this child and all of its children + } - // add the block - newblocks.append( block ); - // add all children of block that should be after block - for ( const auto child : nif->getChildLinks( block ) ) { - if ( !childBeforeParent( nif, child ) ) - addTree( nif, child, newblocks ); // now add this child and all of its children - } + // add the block + newblocks.append( block ); + // add all children of block that should be after block + for ( const auto child : nif->getChildLinks( block ) ) { + if ( !childBeforeParent( nif, child ) ) + addTree( nif, child, newblocks ); // now add this child and all of its children } +} - QModelIndex cast( NifModel * nif, const QModelIndex & ) override final +QModelIndex spSanitizeBlockOrder::cast( NifModel * nif, const QModelIndex & ) +{ + // list of root blocks + QList rootblocks = nif->getRootLinks(); + + // list of blocks that have been added + // newblocks[0] is the block number of the block that must be + // assigned number 0 + // newblocks[1] is the block number of the block that must be + // assigned number 1 + // etc. + QList newblocks; + + // add blocks recursively + for ( const auto rootblock : rootblocks ) { - // list of root blocks - QList rootblocks = nif->getRootLinks(); - - // list of blocks that have been added - // newblocks[0] is the block number of the block that must be - // assigned number 0 - // newblocks[1] is the block number of the block that must be - // assigned number 1 - // etc. - QList newblocks; - - // add blocks recursively - for ( const auto rootblock : rootblocks ) - { - addTree( nif, rootblock, newblocks ); - } + addTree( nif, rootblock, newblocks ); + } - // check whether all blocks have been added - if ( nif->getBlockCount() != newblocks.size() ) { - qCCritical( nsSpell ) << Spell::tr( "failed to sanitize blocks order, corrupt nif tree?" ); - return QModelIndex(); - } + // check whether all blocks have been added + if ( nif->getBlockCount() != newblocks.size() ) { + qCCritical( nsSpell ) << Spell::tr( "failed to sanitize blocks order, corrupt nif tree?" ); + return QModelIndex(); + } - // invert mapping - QVector order( nif->getBlockCount() ); + // invert mapping + QVector order( nif->getBlockCount() ); - for ( qint32 n = 0; n < newblocks.size(); n++ ) { - order[newblocks[n]] = n; - //qDebug() << n << newblocks[n]; - } + for ( qint32 n = 0; n < newblocks.size(); n++ ) { + order[newblocks[n]] = n; + //qDebug() << n << newblocks[n]; + } - // reorder the blocks - nif->reorderBlocks( order ); + // reorder the blocks + nif->reorderBlocks( order ); - return QModelIndex(); - } -}; + return QModelIndex(); +} REGISTER_SPELL( spSanitizeBlockOrder ) diff --git a/src/spells/sanitize.h b/src/spells/sanitize.h new file mode 100644 index 000000000..69099f610 --- /dev/null +++ b/src/spells/sanitize.h @@ -0,0 +1,27 @@ +#ifndef SANITIZE_H +#define SANITIZE_H + +//! Reorders blocks +class spSanitizeBlockOrder final : public Spell +{ +public: + QString name() const override final { return Spell::tr( "Reorder Blocks" ); } + QString page() const override final { return Spell::tr( "Sanitize" ); } + // Prevent this from running during auto-sanitize for the time being + // Can really only cause issues with rendering and textureset overrides via the CK + bool sanity() const { return false; } + + bool isApplicable( const NifModel *, const QModelIndex & index ) override final; + + // check whether the block is of a type that comes before the parent or not + bool childBeforeParent( NifModel * nif, qint32 block ); + + // build the nif tree at node block; the block itself and its children are recursively added to + // the newblocks list + void addTree( NifModel * nif, qint32 block, QList & newblocks ); + + QModelIndex cast( NifModel * nif, const QModelIndex & ) override final; +}; + + +#endif // SANITIZE_H From 97c346604cc4e387fd9a7f0ddaf3da2de8928a91 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Thu, 16 Nov 2017 04:21:42 -0500 Subject: [PATCH 091/152] [GL] Fix potential crash from 7f57f23 It was possible to enter a neverending loop if the texture was not immediately found after cleaning the path in that scope. --- src/gl/gltex.cpp | 2 +- src/glview.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gl/gltex.cpp b/src/gl/gltex.cpp index 0bccf5f3e..71df658b7 100644 --- a/src/gl/gltex.cpp +++ b/src/gl/gltex.cpp @@ -240,7 +240,7 @@ QString TexCache::find( const QString & file, const QString & nifdir, QByteArray } // For Skyrim and FO4 which occasionally leave the textures off - if ( !filename.startsWith( "textures" ) ) { + if ( !filename.startsWith( "textures", Qt::CaseInsensitive ) ) { QRegularExpression re( "textures[\\\\/]", QRegularExpression::CaseInsensitiveOption ); int texIdx = filename.indexOf( re ); if ( texIdx > 0 ) { diff --git a/src/glview.cpp b/src/glview.cpp index 5e3110726..e82237f51 100644 --- a/src/glview.cpp +++ b/src/glview.cpp @@ -447,8 +447,8 @@ void GLView::paintGL() } viewTrans.rotation.fromEuler( Rot[0] / 180.0 * PI, Rot[1] / 180.0 * PI, Rot[2] / 180.0 * PI ); - viewTrans.rotation = viewTrans.rotation * ap; viewTrans.translation = viewTrans.rotation * Pos; + viewTrans.rotation = viewTrans.rotation * ap; if ( view != ViewWalk ) viewTrans.translation[2] -= Dist * 2; From 067a78989a0746cd1f6ec3121a89655c5de2b5cb Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Thu, 16 Nov 2017 04:37:01 -0500 Subject: [PATCH 092/152] [UI] Allow HDR Color3/Color4 In some cases RGBA exceed 1.0 and any attempt to edit these colors in NifSkope will truncate the values over 1.0. There was no way to set values over 1.0 whatsoever. BSEffectShaderProperty's base color alpha most commonly uses values > 1.0. --- src/data/niftypes.h | 6 ++++++ src/data/nifvalue.cpp | 33 ++++++++++++++++++++++++--------- src/ui/widgets/colorwheel.cpp | 6 ++++++ src/ui/widgets/valueedit.cpp | 8 ++++---- 4 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/data/niftypes.h b/src/data/niftypes.h index 0fa55a138..f82af4915 100644 --- a/src/data/niftypes.h +++ b/src/data/niftypes.h @@ -1259,6 +1259,12 @@ class Color3 return ( c -= o ); } + //! Equality operator + bool operator==( const Color3 & c ) const + { + return rgb[0] == c.rgb[0] && rgb[1] == c.rgb[1] && rgb[2] == c.rgb[2]; + } + //! Get the red component float red() const { return rgb[0]; } //! Get the green component diff --git a/src/data/nifvalue.cpp b/src/data/nifvalue.cpp index c0679f7e8..99292a56d 100644 --- a/src/data/nifvalue.cpp +++ b/src/data/nifvalue.cpp @@ -598,7 +598,7 @@ bool NifValue::operator==( const NifValue & other ) const if ( !c1 || !c2 ) return false; - return c1->toQColor() == c2->toQColor(); + return *c1 == *c2; } case tColor4: @@ -610,7 +610,7 @@ bool NifValue::operator==( const NifValue & other ) const if ( !c1 || !c2 ) return false; - return c1->toQColor() == c2->toQColor(); + return *c1 == *c2; } case tVector2: @@ -882,20 +882,35 @@ QString NifValue::toString() const case tColor3: { Color3 * col = static_cast( val.data ); + float r = col->red(), g = col->green(), b = col->blue(); + + // HDR Colors + if ( r > 1.0 || g > 1.0 || b > 1.0 ) + return QString( "R %1 G %2 B %3" ).arg( r, 0, 'f', 3 ).arg( g, 0, 'f', 3 ).arg( b, 0, 'f', 3 ); + return QString( "#%1%2%3" ) - .arg( (int)( col->red() * 0xff ), 2, 16, QChar( '0' ) ) - .arg( (int)( col->green() * 0xff ), 2, 16, QChar( '0' ) ) - .arg( (int)( col->blue() * 0xff ), 2, 16, QChar( '0' ) ); + .arg( (int)( r * 0xff ), 2, 16, QChar( '0' ) ) + .arg( (int)( g * 0xff ), 2, 16, QChar( '0' ) ) + .arg( (int)( b * 0xff ), 2, 16, QChar( '0' ) ); } case tColor4: case tByteColor4: { Color4 * col = static_cast( val.data ); + float r = col->red(), g = col->green(), b = col->blue(), a = col->alpha(); + + // HDR Colors + if ( r > 1.0 || g > 1.0 || b > 1.0 || a > 1.0 ) + return QString( "R %1 G %2 B %3 A %4" ).arg( r, 0, 'f', 3 ) + .arg( g, 0, 'f', 3 ) + .arg( b, 0, 'f', 3 ) + .arg( a, 0, 'f', 3 ); + return QString( "#%1%2%3%4" ) - .arg( (int)( col->red() * 0xff ), 2, 16, QChar( '0' ) ) - .arg( (int)( col->green() * 0xff ), 2, 16, QChar( '0' ) ) - .arg( (int)( col->blue() * 0xff ), 2, 16, QChar( '0' ) ) - .arg( (int)( col->alpha() * 0xff ), 2, 16, QChar( '0' ) ); + .arg( (int)( r * 0xff ), 2, 16, QChar( '0' ) ) + .arg( (int)( g * 0xff ), 2, 16, QChar( '0' ) ) + .arg( (int)( b * 0xff ), 2, 16, QChar( '0' ) ) + .arg( (int)( a * 0xff ), 2, 16, QChar( '0' ) ); } case tVector2: case tHalfVector2: diff --git a/src/ui/widgets/colorwheel.cpp b/src/ui/widgets/colorwheel.cpp index 4c0e6281b..2282313aa 100644 --- a/src/ui/widgets/colorwheel.cpp +++ b/src/ui/widgets/colorwheel.cpp @@ -421,11 +421,17 @@ QColor ColorWheel::choose( const QColor & c, bool alphaEnable, QWidget * parent Color3 ColorWheel::choose( const Color3 & c, QWidget * parent ) { + if ( c.red() > 1.0 || c.green() > 1.0 || c.blue() > 1.0 ) + return c; + return Color3( choose( c.toQColor(), false, parent ) ); } Color4 ColorWheel::choose( const Color4 & c, QWidget * parent ) { + if ( c.red() > 1.0 || c.green() > 1.0 || c.blue() > 1.0 || c.alpha() > 1.0 ) + return c; + return Color4( choose( c.toQColor(), true, parent ) ); } diff --git a/src/ui/widgets/valueedit.cpp b/src/ui/widgets/valueedit.cpp index 2912be328..2c8a260c6 100644 --- a/src/ui/widgets/valueedit.cpp +++ b/src/ui/widgets/valueedit.cpp @@ -540,25 +540,25 @@ ColorEdit::ColorEdit( QWidget * parent ) : ValueEdit( parent ) lay->addWidget( new CenterLabel( "R" ), 1 ); lay->addWidget( r = new QDoubleSpinBox, 5 ); r->setDecimals( COLOR_DECIMALS ); - r->setRange( 0, 1 ); + r->setRange( 0, FLT_MAX ); r->setSingleStep( COLOR_STEP ); connect( r, dsbValueChanged, this, &ColorEdit::sltChanged ); lay->addWidget( new CenterLabel( "G" ), 1 ); lay->addWidget( g = new QDoubleSpinBox, 5 ); g->setDecimals( COLOR_DECIMALS ); - g->setRange( 0, 1 ); + g->setRange( 0, FLT_MAX ); g->setSingleStep( COLOR_STEP ); connect( g, dsbValueChanged, this, &ColorEdit::sltChanged ); lay->addWidget( new CenterLabel( "B" ), 1 ); lay->addWidget( b = new QDoubleSpinBox, 5 ); b->setDecimals( COLOR_DECIMALS ); - b->setRange( 0, 1 ); + b->setRange( 0, FLT_MAX ); b->setSingleStep( COLOR_STEP ); connect( b, dsbValueChanged, this, &ColorEdit::sltChanged ); lay->addWidget( al = new CenterLabel( "A" ), 1 ); lay->addWidget( a = new QDoubleSpinBox, 5 ); a->setDecimals( COLOR_DECIMALS ); - a->setRange( 0, 1 ); + a->setRange( 0, FLT_MAX ); a->setSingleStep( COLOR_STEP ); connect( a, dsbValueChanged, this, &ColorEdit::sltChanged ); From a2011d42cbe8a358431a68b5300bc5acbc4576c7 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Thu, 16 Nov 2017 04:41:24 -0500 Subject: [PATCH 093/152] [Spells] Fix Calc Spring Lengths for nifxml 0.9 --- src/spells/havok.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/spells/havok.cpp b/src/spells/havok.cpp index ed83eb0f2..5691ed6e8 100644 --- a/src/spells/havok.cpp +++ b/src/spells/havok.cpp @@ -357,6 +357,9 @@ class spStiffSpringHelper final : public Spell QModelIndex cast( NifModel * nif, const QModelIndex & idx ) override final { QModelIndex iConstraint = nif->getBlock( idx ); + QModelIndex iSpring = nif->getIndex( iConstraint, "Stiff Spring" ); + if ( !iSpring.isValid() ) + iSpring = iConstraint; QModelIndex iBodyA = nif->getBlock( nif->getLink( nif->getIndex( iConstraint, "Entities" ).child( 0, 0 ) ), "bhkRigidBody" ); QModelIndex iBodyB = nif->getBlock( nif->getLink( nif->getIndex( iConstraint, "Entities" ).child( 1, 0 ) ), "bhkRigidBody" ); @@ -369,14 +372,14 @@ class spStiffSpringHelper final : public Spell Transform transA = spConstraintHelper::bodyTrans( nif, iBodyA ); Transform transB = spConstraintHelper::bodyTrans( nif, iBodyB ); - Vector3 pivotA( nif->get( iConstraint, "Pivot A" ) * 7 ); - Vector3 pivotB( nif->get( iConstraint, "Pivot B" ) * 7 ); + Vector3 pivotA( nif->get( iSpring, "Pivot A" ) * 7 ); + Vector3 pivotB( nif->get( iSpring, "Pivot B" ) * 7 ); float length = ( transA * pivotA - transB * pivotB ).length() / 7; - nif->set( iConstraint, "Length", length ); + nif->set( iSpring, "Length", length ); - return nif->getIndex( iConstraint, "Length" ); + return nif->getIndex( iSpring, "Length" ); } }; From a728167a47f31d79aea4ccbbacd114f8e0f58725 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Thu, 16 Nov 2017 05:40:54 -0500 Subject: [PATCH 094/152] [UI] Filter added archives, correct modifyPane behavior Add archives from the file dialog only if they contain a textures or materials folder, the same as done with the Auto Detect button. Fixed some modifyPane behavior for Resources. Disallowed manual editing of list items in Archives, but maintained it in Paths. --- lib/fsengine/fsmanager.cpp | 15 ++++++++++----- lib/fsengine/fsmanager.h | 3 +++ src/ui/settingspane.cpp | 13 +++++++++++-- src/ui/settingsresources.ui | 6 +++++- 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/lib/fsengine/fsmanager.cpp b/lib/fsengine/fsmanager.cpp index 33c5179c3..e6531be5a 100644 --- a/lib/fsengine/fsmanager.cpp +++ b/lib/fsengine/fsmanager.cpp @@ -132,8 +132,13 @@ QStringList FSManager::autodetectArchives( const QString & folder ) list << regPathBSAList( "HKEY_LOCAL_MACHINE\\SOFTWARE\\Bethesda Softworks\\Skyrim Special Edition", "Data" ); #endif + return filterArchives( list, folder ); +} + +QStringList FSManager::filterArchives( const QStringList & list, const QString & folder ) +{ + QStringList listCopy; if ( !folder.isEmpty() ) { - QStringList listCopy; // Looking for a specific folder here // Remove the BSAs that do not contain this folder for ( auto f : list ) { @@ -142,15 +147,15 @@ QStringList FSManager::autodetectArchives( const QString & folder ) auto bsa = handler->getArchive(); if ( bsa ) { auto rootFolder = bsa->getFolder( "" ); - if ( rootFolder->children.contains( folder ) ) { + if ( rootFolder->children.contains( folder.toLower() ) ) { listCopy.append( f ); } } } } - - list = listCopy; + } else { + listCopy = list; } - return list; + return listCopy; } diff --git a/lib/fsengine/fsmanager.h b/lib/fsengine/fsmanager.h index 5b435fb3b..20e460e35 100644 --- a/lib/fsengine/fsmanager.h +++ b/lib/fsengine/fsmanager.h @@ -58,6 +58,9 @@ class FSManager : public QObject //! Gets the list of globally registered BSA files static QList archiveList(); + //! Filters a list of BSAs from a provided list + static QStringList filterArchives( const QStringList & list, const QString & folder = "" ); + protected: //! Constructor FSManager( QObject * parent = nullptr ); diff --git a/src/ui/settingspane.cpp b/src/ui/settingspane.cpp index 55f6d5bf7..95744f8f3 100644 --- a/src/ui/settingspane.cpp +++ b/src/ui/settingspane.cpp @@ -533,6 +533,9 @@ SettingsResources::SettingsResources( QWidget * parent ) : ui->btnFolderAutoDetect->setHidden( true ); #endif + connect( ui->foldersList, &QListView::doubleClicked, this, &SettingsPane::modifyPane ); + connect( ui->chkAlternateExt, &QCheckBox::clicked, this, &SettingsPane::modifyPane ); + // Move Up / Move Down Behavior connect( ui->foldersList->selectionModel(), &QItemSelectionModel::currentChanged, [this]( const QModelIndex & idx, const QModelIndex & last ) @@ -752,9 +755,15 @@ void SettingsResources::on_btnArchiveAdd_clicked() "Archive (*.bsa *.ba2)" ); - for ( int i = 0; i < files.count(); i++ ) { + QStringList filtered; + + filtered += FSManager::filterArchives( files, "textures" ); + filtered += FSManager::filterArchives( files, "materials" ); + filtered.removeDuplicates(); + + for ( int i = 0; i < filtered.count(); i++ ) { archives->insertRow( i ); - archives->setData( archives->index( i, 0 ), files.at( i ) ); + archives->setData( archives->index( i, 0 ), filtered.at( i ) ); } ui->archivesList->setCurrentIndex( archives->index( 0, 0 ) ); diff --git a/src/ui/settingsresources.ui b/src/ui/settingsresources.ui index 9a1dd45ce..0a6db16c5 100644 --- a/src/ui/settingsresources.ui +++ b/src/ui/settingsresources.ui @@ -178,7 +178,11 @@ Game Paths 0
- + + + QAbstractItemView::NoEditTriggers + + From eb9f6fca594efaf42dc68c18ec3e13291d8c8d34 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Thu, 16 Nov 2017 11:29:27 -0500 Subject: [PATCH 095/152] [BSA] Support sRGB BCn files --- lib/fsengine/bsa.cpp | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/fsengine/bsa.cpp b/lib/fsengine/bsa.cpp index 88b9255a3..df5d8bd21 100644 --- a/lib/fsengine/bsa.cpp +++ b/lib/fsengine/bsa.cpp @@ -558,15 +558,6 @@ bool BSA::fileContents( const QString & fn, QByteArray & content ) ddsHeader.dwPitchOrLinearSize = file->tex.header.width * file->tex.header.height; // 8bpp break; - case DXGI_FORMAT_BC7_UNORM: - ddsHeader.ddspf.dwFlags = DDS_FOURCC; - ddsHeader.ddspf.dwFourCC = MAKEFOURCC( 'D', 'X', '1', '0' ); - ddsHeader.dwPitchOrLinearSize = file->tex.header.width * file->tex.header.height; // 8bpp - - dx10 = true; - dx10Header.dxgiFormat = DXGI_FORMAT_BC7_UNORM; - break; - case DXGI_FORMAT_B8G8R8A8_UNORM: ddsHeader.ddspf.dwFlags = DDS_RGBA; ddsHeader.ddspf.dwRGBBitCount = 32; @@ -584,6 +575,19 @@ bool BSA::fileContents( const QString & fn, QByteArray & content ) ddsHeader.dwPitchOrLinearSize = file->tex.header.width * file->tex.header.height; // 8bpp break; + case DXGI_FORMAT_BC7_UNORM: + case DXGI_FORMAT_BC1_UNORM_SRGB: + case DXGI_FORMAT_BC2_UNORM_SRGB: + case DXGI_FORMAT_BC3_UNORM_SRGB: + case DXGI_FORMAT_BC7_UNORM_SRGB: + ddsHeader.ddspf.dwFlags = DDS_FOURCC; + ddsHeader.ddspf.dwFourCC = MAKEFOURCC( 'D', 'X', '1', '0' ); + ddsHeader.dwPitchOrLinearSize = file->tex.header.width * file->tex.header.height; + + dx10 = true; + dx10Header.dxgiFormat = DXGI_FORMAT( file->tex.header.format ); + break; + default: supported = false; break; From 72797e88f3e0c3e5b36394573a5c920976321498 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Thu, 16 Nov 2017 11:44:23 -0500 Subject: [PATCH 096/152] [Build] Cross-platform fixes Include climits for USHRT_MAX on other platforms. --- lib/NvTriStrip/NvTriStrip.cpp | 1 + lib/NvTriStrip/NvTriStripObjects.cpp | 2 ++ src/spells/strippify.cpp | 2 ++ 3 files changed, 5 insertions(+) diff --git a/lib/NvTriStrip/NvTriStrip.cpp b/lib/NvTriStrip/NvTriStrip.cpp index 7f0fb9c50..bd2fb5a9f 100644 --- a/lib/NvTriStrip/NvTriStrip.cpp +++ b/lib/NvTriStrip/NvTriStrip.cpp @@ -2,6 +2,7 @@ #include "NvTriStripObjects.h" #include "NvTriStrip.h" #include // memset +#include //////////////////////////////////////////////////////////////////////////////////////// //private data diff --git a/lib/NvTriStrip/NvTriStripObjects.cpp b/lib/NvTriStrip/NvTriStripObjects.cpp index 7f5bd1774..418e5d847 100644 --- a/lib/NvTriStrip/NvTriStripObjects.cpp +++ b/lib/NvTriStrip/NvTriStripObjects.cpp @@ -7,6 +7,8 @@ #include #include // memset #include // printf +#include + #include "NvTriStripObjects.h" #include "VertexCache.h" diff --git a/src/spells/strippify.cpp b/src/spells/strippify.cpp index e98a7da6d..daaaad737 100644 --- a/src/spells/strippify.cpp +++ b/src/spells/strippify.cpp @@ -4,6 +4,8 @@ #include "lib/nvtristripwrapper.h" +#include + // TODO: Move these to blocks.h / misc.h / wherever template void copyArray( NifModel * nif, const QModelIndex & iDst, const QModelIndex & iSrc ) From e81b248f695c686271b60d95a1b346c7078ef426 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Mon, 27 Nov 2017 18:23:49 -0500 Subject: [PATCH 097/152] [UI] Undo transactions, support for command merging Necessary for upcoming array copy/paste among other things. Allows multiple changes to be done/undone with Ctrl+Y/Z. --- src/model/nifdelegate.cpp | 1 + src/model/undocommands.cpp | 71 +++++++++++++++++++++++++++++++++----- src/model/undocommands.h | 20 +++++++++-- src/ui/widgets/nifview.cpp | 1 + 4 files changed, 83 insertions(+), 10 deletions(-) diff --git a/src/model/nifdelegate.cpp b/src/model/nifdelegate.cpp index 8be578c85..1126646f9 100644 --- a/src/model/nifdelegate.cpp +++ b/src/model/nifdelegate.cpp @@ -298,6 +298,7 @@ class NifDelegate final : public QItemDelegate if ( model->inherits( "NifModel" ) ) { auto valueType = model->sibling( index.row(), 0, index ).data().toString(); + ChangeValueCommand::createTransaction(); auto nif = static_cast(model); nif->undoStack->push( new ChangeValueCommand( index, v, vedit->getValue().toString(), valueType, nif ) ); } diff --git a/src/model/undocommands.cpp b/src/model/undocommands.cpp index 9944730a4..d44f1da2e 100644 --- a/src/model/undocommands.cpp +++ b/src/model/undocommands.cpp @@ -40,16 +40,21 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //! @file undocommands.cpp ChangeValueCommand, ToggleCheckBoxListCommand +size_t ChangeValueCommand::lastID = 0; + /* * ChangeValueCommand */ ChangeValueCommand::ChangeValueCommand( const QModelIndex & index, const QVariant & value, const QString & valueString, const QString & valueType, NifModel * model ) - : QUndoCommand(), nif( model ), idx( index ) + : QUndoCommand(), nif( model ) { - oldValue = index.data( Qt::EditRole ); - newValue = value; + idxs << index; + oldValues << index.data( Qt::EditRole ); + newValues << value; + + localID = lastID; auto oldTxt = index.data( Qt::DisplayRole ).toString(); auto newTxt = valueString; @@ -62,10 +67,13 @@ ChangeValueCommand::ChangeValueCommand( const QModelIndex & index, ChangeValueCommand::ChangeValueCommand( const QModelIndex & index, const NifValue & oldVal, const NifValue & newVal, const QString & valueType, NifModel * model ) - : QUndoCommand(), nif( model ), idx( index ) + : QUndoCommand(), nif( model ) { - oldValue = oldVal.toVariant(); - newValue = newVal.toVariant(); + idxs << index; + oldValues << oldVal.toVariant(); + newValues << newVal.toVariant(); + + localID = lastID; auto oldTxt = oldVal.toString(); auto newTxt = newVal.toString(); @@ -79,7 +87,19 @@ ChangeValueCommand::ChangeValueCommand( const QModelIndex & index, const NifValu void ChangeValueCommand::redo() { //qDebug() << "Redoing"; - nif->setData( idx, newValue, Qt::EditRole ); + Q_ASSERT( idxs.size() == newValues.size() && newValues.size() == oldValues.size() ); + + if ( idxs.size() > 1 ) + nif->setState( BaseModel::Processing ); + + int i = 0; + for ( auto idx : idxs ) + nif->setData( idx, newValues.at(i++), Qt::EditRole ); + + if ( idxs.size() > 1 ) { + nif->restoreState(); + nif->dataChanged( idxs.first(), idxs.last() ); + } //qDebug() << nif->data( idx ).toString(); } @@ -87,11 +107,46 @@ void ChangeValueCommand::redo() void ChangeValueCommand::undo() { //qDebug() << "Undoing"; - nif->setData( idx, oldValue, Qt::EditRole ); + + if ( idxs.size() > 1 ) + nif->setState( BaseModel::Processing ); + + int i = 0; + for ( auto idx : idxs ) + nif->setData( idx, oldValues.at(i++), Qt::EditRole ); + + if ( idxs.size() > 1 ) { + nif->restoreState(); + nif->dataChanged( idxs.first(), idxs.last() ); + } //qDebug() << nif->data( idx ).toString(); } +int ChangeValueCommand::id() const +{ + return localID; +} + +bool ChangeValueCommand::mergeWith( const QUndoCommand * other ) +{ + const auto cv = static_cast(other); + + if ( localID != cv->localID ) + return false; + + idxs << cv->idxs; + newValues << cv->newValues; + oldValues << cv->oldValues; + + return true; +} + +void ChangeValueCommand::createTransaction() +{ + lastID++; +} + /* * ToggleCheckBoxListCommand diff --git a/src/model/undocommands.h b/src/model/undocommands.h index dd91d0c78..e515a545d 100644 --- a/src/model/undocommands.h +++ b/src/model/undocommands.h @@ -52,10 +52,26 @@ class ChangeValueCommand : public QUndoCommand const NifValue & newValue, const QString & valueType, NifModel * model ); void redo() override; void undo() override; + + //! The command ID + int id() const override; + + //! Handle merging of commands in the same transaction + bool mergeWith( const QUndoCommand * command ) override; + + //! Increments the lastID + static void createTransaction(); + private: NifModel * nif; - QVariant newValue, oldValue; - QModelIndex idx; + QVector newValues, oldValues; + QVector idxs; + + //! The command ID for this undo command + size_t localID; + + //! The current command ID for any new undo commands + static size_t lastID; }; diff --git a/src/ui/widgets/nifview.cpp b/src/ui/widgets/nifview.cpp index 58d0afd13..439c396ca 100644 --- a/src/ui/widgets/nifview.cpp +++ b/src/ui/widgets/nifview.cpp @@ -230,6 +230,7 @@ void NifTreeView::pasteTo( QModelIndex idx ) void NifTreeView::paste() { + ChangeValueCommand::createTransaction(); QModelIndexList idx = selectionModel()->selectedIndexes(); for ( const auto i : idx ) { pasteTo( i ); From aee55a7e82c50294af290b36871139e7dabf07c8 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 28 Nov 2017 16:16:34 -0500 Subject: [PATCH 098/152] [GL] Fix rigidbody axes' size and color in select buffer Also only show rigid body axes when Show Axes is on. --- src/gl/glnode.cpp | 15 ++++++++------- src/gl/gltools.cpp | 11 +++++++---- src/gl/gltools.h | 2 +- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/gl/glnode.cpp b/src/gl/glnode.cpp index 352b2a349..a97a30819 100644 --- a/src/gl/glnode.cpp +++ b/src/gl/glnode.cpp @@ -1597,17 +1597,18 @@ void Node::drawHavok() drawHvkShape( nif, nif->getBlock( nif->getLink( iBody, "Shape" ) ), shapeStack, scene, colors[ color_index ] ); - if ( Node::SELECTING ) { + + // Scale up for Skyrim + float havokScale = (nif->checkVersion( 0x14020007, 0x14020007 ) && nif->getUserVersion() >= 12) ? 10.0f : 1.0f; + + if ( Node::SELECTING && scene->options & Scene::ShowAxes ) { int s_nodeId = ID2COLORKEY( nif->getBlockNumber( iBody ) ); glColor4ubv( (GLubyte *)&s_nodeId ); glDepthFunc( GL_ALWAYS ); - drawAxes( Vector3( nif->get( iBody, "Center" ) ), 2.0f ); + drawAxes( Vector3( nif->get( iBody, "Center" ) ) * havokScale, 1.0f, false ); glDepthFunc( GL_LEQUAL ); - } else { - // Scale up for Skyrim - float havokScale = (nif->checkVersion( 0x14020007, 0x14020007 ) && nif->getUserVersion() >= 12) ? 10.0f : 1.0f; - - drawAxes( Vector3( nif->get( iBody, "Center" ) ) * havokScale, 2.0f ); + } else if ( scene->options & Scene::ShowAxes ) { + drawAxes( Vector3( nif->get( iBody, "Center" ) ) * havokScale, 1.0f ); } glPopMatrix(); diff --git a/src/gl/gltools.cpp b/src/gl/gltools.cpp index 326bf9e8b..1531e0ea8 100644 --- a/src/gl/gltools.cpp +++ b/src/gl/gltools.cpp @@ -237,13 +237,14 @@ BoundSphere operator*( const Transform & t, const BoundSphere & sphere ) * draw primitives */ -void drawAxes( const Vector3 & c, float axis ) +void drawAxes( const Vector3 & c, float axis, bool color ) { glPushMatrix(); glTranslate( c ); GLfloat arrow = axis / 36.0; glBegin( GL_LINES ); - glColor3f( 1.0, 0.0, 0.0 ); + if ( color ) + glColor3f( 1.0, 0.0, 0.0 ); glVertex3f( -axis, 0, 0 ); glVertex3f( +axis, 0, 0 ); glVertex3f( +axis, 0, 0 ); @@ -254,7 +255,8 @@ void drawAxes( const Vector3 & c, float axis ) glVertex3f( +axis - 3 * arrow, +arrow, -arrow ); glVertex3f( +axis, 0, 0 ); glVertex3f( +axis - 3 * arrow, -arrow, -arrow ); - glColor3f( 0.0, 1.0, 0.0 ); + if ( color ) + glColor3f( 0.0, 1.0, 0.0 ); glVertex3f( 0, -axis, 0 ); glVertex3f( 0, +axis, 0 ); glVertex3f( 0, +axis, 0 ); @@ -265,7 +267,8 @@ void drawAxes( const Vector3 & c, float axis ) glVertex3f( +arrow, +axis - 3 * arrow, -arrow ); glVertex3f( 0, +axis, 0 ); glVertex3f( -arrow, +axis - 3 * arrow, -arrow ); - glColor3f( 0.0, 0.0, 1.0 ); + if ( color ) + glColor3f( 0.0, 0.0, 1.0 ); glVertex3f( 0, 0, -axis ); glVertex3f( 0, 0, +axis ); glVertex3f( 0, 0, +axis ); diff --git a/src/gl/gltools.h b/src/gl/gltools.h index 887d185e0..b3a440545 100644 --- a/src/gl/gltools.h +++ b/src/gl/gltools.h @@ -113,7 +113,7 @@ class SkinPartition final QVector sortAxes( QVector axesDots ); -void drawAxes( const Vector3 & c, float axis ); +void drawAxes( const Vector3 & c, float axis, bool color = true ); void drawAxesOverlay( const Vector3 & c, float axis, QVector axesOrder = {2, 1, 0} ); void drawGrid( int s, int line, int sub ); void drawBox( const Vector3 & a, const Vector3 & b ); From 1b471d88386a0bc6b42d0ac27446a4e6044d375f Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 28 Nov 2017 16:19:59 -0500 Subject: [PATCH 099/152] [UI] Prevent Redo from crashing when indices no longer exist Switch to QPersistentModelIndex and check index validity before setting data. --- src/model/undocommands.cpp | 18 ++++++++++++------ src/model/undocommands.h | 4 ++-- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/model/undocommands.cpp b/src/model/undocommands.cpp index d44f1da2e..d49a0e445 100644 --- a/src/model/undocommands.cpp +++ b/src/model/undocommands.cpp @@ -93,8 +93,10 @@ void ChangeValueCommand::redo() nif->setState( BaseModel::Processing ); int i = 0; - for ( auto idx : idxs ) - nif->setData( idx, newValues.at(i++), Qt::EditRole ); + for ( auto idx : idxs ) { + if ( idx.isValid() ) + nif->setData( idx, newValues.at( i++ ), Qt::EditRole ); + } if ( idxs.size() > 1 ) { nif->restoreState(); @@ -112,8 +114,10 @@ void ChangeValueCommand::undo() nif->setState( BaseModel::Processing ); int i = 0; - for ( auto idx : idxs ) - nif->setData( idx, oldValues.at(i++), Qt::EditRole ); + for ( auto idx : idxs ) { + if ( idx.isValid() ) + nif->setData( idx, oldValues.at( i++ ), Qt::EditRole ); + } if ( idxs.size() > 1 ) { nif->restoreState(); @@ -167,7 +171,8 @@ ToggleCheckBoxListCommand::ToggleCheckBoxListCommand( const QModelIndex & index, void ToggleCheckBoxListCommand::redo() { //qDebug() << "Redoing"; - nif->setData( idx, newValue, Qt::EditRole ); + if ( idx.isValid() ) + nif->setData( idx, newValue, Qt::EditRole ); //qDebug() << nif->data( idx ).toString(); } @@ -175,7 +180,8 @@ void ToggleCheckBoxListCommand::redo() void ToggleCheckBoxListCommand::undo() { //qDebug() << "Undoing"; - nif->setData( idx, oldValue, Qt::EditRole ); + if ( idx.isValid() ) + nif->setData( idx, oldValue, Qt::EditRole ); //qDebug() << nif->data( idx ).toString(); } diff --git a/src/model/undocommands.h b/src/model/undocommands.h index e515a545d..a7eef3ff7 100644 --- a/src/model/undocommands.h +++ b/src/model/undocommands.h @@ -65,7 +65,7 @@ class ChangeValueCommand : public QUndoCommand private: NifModel * nif; QVector newValues, oldValues; - QVector idxs; + QVector idxs; //! The command ID for this undo command size_t localID; @@ -84,7 +84,7 @@ class ToggleCheckBoxListCommand : public QUndoCommand private: NifModel * nif; QVariant newValue, oldValue; - QModelIndex idx; + QPersistentModelIndex idx; }; From aa674190640b04bcb1e4d55cae304328969651fa Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 28 Nov 2017 16:29:50 -0500 Subject: [PATCH 100/152] [UI] Speed up Row Hiding in large flat arrays The further into a large array the slower this recursive function gets. In very large vertex/normal/color arrays it slowed down selection via arrow key significantly. --- src/ui/widgets/nifview.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ui/widgets/nifview.cpp b/src/ui/widgets/nifview.cpp index 439c396ca..6c71eec78 100644 --- a/src/ui/widgets/nifview.cpp +++ b/src/ui/widgets/nifview.cpp @@ -262,6 +262,10 @@ void NifTreeView::updateConditionRecurse( const QModelIndex & index ) if ( !item ) return; + // Skip flat array items + if ( item->parent() && item->parent()->isArray() && !item->childCount() ) + return; + for ( int r = 0; r < model()->rowCount( index ); r++ ) { QModelIndex child = model()->index( r, 0, index ); updateConditionRecurse( child ); From 26b602677ff90a349e24f0f735c01da470648ea7 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 28 Nov 2017 18:00:06 -0500 Subject: [PATCH 101/152] [UI] Auto-expand more items in Block Details Expanded Textures array expansion to a more general "end array" expansion. Will now apply to Connect Points, etc. Expand NiSkinPartition partition array always, expand NiQuatTransform in interpolators always, expand Children array of NiNode if relatively small. --- src/ui/widgets/nifview.cpp | 33 ++++++++++++++++++++++++++++----- src/ui/widgets/nifview.h | 3 +++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/ui/widgets/nifview.cpp b/src/ui/widgets/nifview.cpp index 6c71eec78..9d9f6da6f 100644 --- a/src/ui/widgets/nifview.cpp +++ b/src/ui/widgets/nifview.cpp @@ -385,19 +385,42 @@ void NifTreeView::currentChanged( const QModelIndex & current, const QModelIndex updateConditionRecurse( current ); } + autoExpanded = false; auto mdl = static_cast( nif ); - if ( mdl ) { - // Auto-Expand Textures - if ( mdl->inherits( current, "BSShaderTextureSet" ) ) { - expand( current.child( 1, 0 ) ); + if ( mdl && mdl->isNiBlock( current ) ) { + auto cnt = mdl->rowCount( current ); + const int ARRAY_LIMIT = 100; + if ( mdl->inherits( current, "NiTransformInterpolator" ) + || mdl->inherits( current, "NiBSplineTransformInterpolator" ) ) { + // Auto-Expand NiQuatTransform + autoExpand( current.child( 0, 0 ) ); + } else if ( mdl->inherits( current, "NiNode" ) ) { + // Auto-Expand Children array + auto iChildren = mdl->getIndex( current, "Children" ); + if ( mdl->rowCount( iChildren ) < ARRAY_LIMIT ) + autoExpand( iChildren ); + } else if ( mdl->inherits( current, "NiSkinPartition" ) ) { + // Auto-Expand skin partitions array + autoExpand( current.child( 1, 0 ) ); + } else if ( mdl->getValue( current.child( cnt - 1, 0 ) ).type() == NifValue::tNone + && mdl->rowCount( current.child( cnt - 1, 0 ) ) < ARRAY_LIMIT ) { + // Auto-Expand final arrays/compounds + autoExpand( current.child( cnt - 1, 0 ) ); } } emit sigCurrentIndexChanged( currentIndex() ); } +void NifTreeView::autoExpand( const QModelIndex & index ) +{ + autoExpanded = true; + expand( index ); +} + void NifTreeView::scrollExpand( const QModelIndex & index ) { // this is a compromise between scrolling to the top, and scrolling the last child to the bottom - scrollTo( index, PositionAtCenter ); + if ( !autoExpanded ) + scrollTo( index, PositionAtCenter ); } diff --git a/src/ui/widgets/nifview.h b/src/ui/widgets/nifview.h index b68f045f4..bd733d398 100644 --- a/src/ui/widgets/nifview.h +++ b/src/ui/widgets/nifview.h @@ -95,7 +95,10 @@ protected slots: QStyleOptionViewItem viewOptions() const override final; + void autoExpand( const QModelIndex & index ); + bool doRowHiding = true; + bool autoExpanded = false; class BaseModel * nif = nullptr; From 3c84be607df1ff05ee7e697355c9a8cccc000d70 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 28 Nov 2017 18:11:47 -0500 Subject: [PATCH 102/152] [UI] Flat Array/Compound copy/paste Can copy/paste flat arrays or compounds with Ctrl+C/V on the parent branch. Useful for arrays such as vertex colors, or compounds like transforms, connect points, bounding spheres. --- src/ui/widgets/nifview.cpp | 125 ++++++++++++++++++++++++++----------- src/ui/widgets/nifview.h | 18 +++++- 2 files changed, 107 insertions(+), 36 deletions(-) diff --git a/src/ui/widgets/nifview.cpp b/src/ui/widgets/nifview.cpp index 9d9f6da6f..0bf8a26e8 100644 --- a/src/ui/widgets/nifview.cpp +++ b/src/ui/widgets/nifview.cpp @@ -42,6 +42,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include + NifTreeView::NifTreeView( QWidget * parent, Qt::WindowFlags flags ) : QTreeView() { Q_UNUSED( flags ); @@ -138,34 +140,44 @@ void NifTreeView::copy() { QModelIndex idx = selectionModel()->selectedIndexes().first(); auto item = static_cast(idx.internalPointer()); - if ( item ) + if ( !item ) + return; + + if ( !item->isArray() && !item->isCompound() ) { valueClipboard->setValue( item->value() ); + } else { + std::vector v; + v.reserve( item->childCount() ); + for ( const auto i : item->children() ) + v.push_back( i->value() ); + + valueClipboard->setValues( v ); + } } -void NifTreeView::pasteTo( QModelIndex idx ) -{ - NifItem * item = static_cast(idx.internalPointer()); +void NifTreeView::pasteTo( QModelIndex iDest, const NifValue & srcValue ) +{ // Only run once per row for the correct column - if ( idx.column() != NifModel::ValueCol ) + if ( iDest.column() != NifModel::ValueCol ) return; - auto valueType = model()->sibling( idx.row(), 0, idx ).data().toString(); + NifItem * item = static_cast(iDest.internalPointer()); - auto copyVal = valueClipboard->getValue(); + auto valueType = model()->sibling( iDest.row(), 0, iDest ).data().toString(); - NifValue dest = item->value(); - if ( dest.type() != copyVal.type() ) + NifValue destValue = item->value(); + if ( destValue.type() != srcValue.type() ) return; - switch ( item->value().type() ) { + switch ( destValue.type() ) { case NifValue::tByte: - nif->set( idx, copyVal.get() ); + nif->set( iDest, srcValue.get() ); break; case NifValue::tWord: case NifValue::tShort: case NifValue::tFlags: case NifValue::tBlockTypeIndex: - nif->set( idx, copyVal.get() ); + nif->set( iDest, srcValue.get() ); break; case NifValue::tStringOffset: case NifValue::tInt: @@ -174,40 +186,40 @@ void NifTreeView::pasteTo( QModelIndex idx ) case NifValue::tStringIndex: case NifValue::tUpLink: case NifValue::tLink: - nif->set( idx, copyVal.get() ); + nif->set( iDest, srcValue.get() ); break; case NifValue::tVector2: case NifValue::tHalfVector2: - nif->set( idx, copyVal.get() ); + nif->set( iDest, srcValue.get() ); break; case NifValue::tVector3: case NifValue::tByteVector3: case NifValue::tHalfVector3: - nif->set( idx, copyVal.get() ); + nif->set( iDest, srcValue.get() ); break; case NifValue::tVector4: - nif->set( idx, copyVal.get() ); + nif->set( iDest, srcValue.get() ); break; case NifValue::tFloat: case NifValue::tHfloat: - nif->set( idx, copyVal.get() ); + nif->set( iDest, srcValue.get() ); break; case NifValue::tColor3: - nif->set( idx, copyVal.get() ); + nif->set( iDest, srcValue.get() ); break; case NifValue::tColor4: case NifValue::tByteColor4: - nif->set( idx, copyVal.get() ); + nif->set( iDest, srcValue.get() ); break; case NifValue::tQuat: case NifValue::tQuatXYZW: - nif->set( idx, copyVal.get() ); + nif->set( iDest, srcValue.get() ); break; case NifValue::tMatrix: - nif->set( idx, copyVal.get() ); + nif->set( iDest, srcValue.get() ); break; case NifValue::tMatrix4: - nif->set( idx, copyVal.get() ); + nif->set( iDest, srcValue.get() ); break; case NifValue::tString: case NifValue::tSizedString: @@ -216,7 +228,7 @@ void NifTreeView::pasteTo( QModelIndex idx ) case NifValue::tHeaderString: case NifValue::tLineString: case NifValue::tChar8String: - nif->set( idx, copyVal.get() ); + nif->set( iDest, srcValue.get() ); break; default: // Return and do not push to Undo Stack @@ -225,16 +237,39 @@ void NifTreeView::pasteTo( QModelIndex idx ) auto n = static_cast(nif); if ( n ) - n->undoStack->push( new ChangeValueCommand( idx, dest, valueClipboard->getValue(), valueType, n ) ); + n->undoStack->push( new ChangeValueCommand( iDest, destValue, srcValue, valueType, n ) ); } void NifTreeView::paste() { ChangeValueCommand::createTransaction(); - QModelIndexList idx = selectionModel()->selectedIndexes(); - for ( const auto i : idx ) { - pasteTo( i ); + for ( const auto i : valueIndexList( selectionModel()->selectedIndexes() ) ) + pasteTo( i, valueClipboard->getValue() ); +} + +void NifTreeView::pasteArray() +{ + QModelIndexList selected = selectionModel()->selectedIndexes(); + QModelIndexList values = valueIndexList( selected ); + + Q_ASSERT( selected.size() == 10 ); + Q_ASSERT( values.size() == 1 ); + + auto root = values.at( 0 ); + auto cnt = nif->rowCount( root ); + + ChangeValueCommand::createTransaction(); + nif->setState( BaseModel::Processing ); + for ( int i = 0; i < cnt && i < valueClipboard->getValues().size(); i++ ) { + auto iDest = root.child( i, NifModel::ValueCol ); + auto srcValue = valueClipboard->getValues().at( iDest.row() ); + + pasteTo( iDest, srcValue ); } + nif->restoreState(); + + if ( cnt > 0 ) + emit nif->dataChanged( root.child( 0, NifModel::ValueCol ), root.child( cnt - 1, NifModel::ValueCol ) ); } void NifTreeView::drawBranches( QPainter * painter, const QRect & rect, const QModelIndex & index ) const @@ -283,10 +318,22 @@ auto splitMime = []( QString format ) { return false; }; +QModelIndexList NifTreeView::valueIndexList( const QModelIndexList & rows ) const +{ + QModelIndexList values; + for ( int i = NifModel::ValueCol; i < rows.count(); i += NifModel::NumColumns ) + values << rows[i]; + + return values; +} + void NifTreeView::keyPressEvent( QKeyEvent * e ) { - auto details = model()->inherits( "NifModel" ); - if ( details ) { + NifModel * nif = nullptr; + NifProxyModel * proxy = nullptr; + + nif = static_cast(model()); + if ( model()->inherits( "NifModel" ) && nif ) { // Determine if a block or branch has been copied bool hasBlockCopied = false; if ( e->matches( QKeySequence::Copy ) || e->matches( QKeySequence::Paste ) ) { @@ -301,14 +348,25 @@ void NifTreeView::keyPressEvent( QKeyEvent * e ) } } + QModelIndexList selectedRows = selectionModel()->selectedIndexes(); + QModelIndexList valueColumns = valueIndexList( selectedRows ); + auto firstRow = selectedRows.at( 0 ); + auto firstValue = valueColumns.at( 0 ); + if ( e->matches( QKeySequence::Copy ) ) { copy(); // Clear the clipboard in case it holds a block to prevent conflicting behavior QApplication::clipboard()->clear(); return; - } else if ( e->matches( QKeySequence::Paste ) && valueClipboard->getValue().isValid() && !hasBlockCopied ) { + } else if ( e->matches( QKeySequence::Paste ) + && (valueClipboard->getValue().isValid() || valueClipboard->getValues().size() > 0) + && !hasBlockCopied ) { // Do row paste if there is no block/branch copied and the NifValue is valid - paste(); + if ( valueColumns.size() == 1 && nif->rowCount( firstRow ) > 0 ) { + pasteArray(); + } else if ( valueClipboard->getValue().isValid() ) { + paste(); + } return; } } @@ -316,14 +374,11 @@ void NifTreeView::keyPressEvent( QKeyEvent * e ) SpellPtr spell = SpellBook::lookup( QKeySequence( e->modifiers() + e->key() ) ); if ( spell ) { - NifModel * nif = nullptr; - NifProxyModel * proxy = nullptr; - QPersistentModelIndex oldidx; // Clear this on any spell cast to prevent it overriding other paste behavior like block -> link row // TODO: Value clipboard does not get cleared when using the context menu. - valueClipboard->getValue().clear(); + valueClipboard->clear(); if ( model()->inherits( "NifModel" ) ) { nif = static_cast( model() ); diff --git a/src/ui/widgets/nifview.h b/src/ui/widgets/nifview.h index bd733d398..749c94655 100644 --- a/src/ui/widgets/nifview.h +++ b/src/ui/widgets/nifview.h @@ -106,8 +106,13 @@ protected slots: void copy(); //! Row Paste void paste(); - void pasteTo( QModelIndex idx ); + void pasteTo( QModelIndex idx, const NifValue & srcValue ); + //! Array/Compound Paste + void pasteArray(); + + //! Get a list of only the value column fields from lists of rows + QModelIndexList valueIndexList( const QModelIndexList & rows ) const; }; @@ -119,8 +124,19 @@ class NifValueClipboard NifValue getValue() { return value; } void setValue( const NifValue & val ) { value = val; } + const std::vector& getValues() const { return values; } + void setValues( const std::vector& vals ) { values = vals; } + + void clear() + { + value.clear(); + } + private: + //! The value stored from a single row copy NifValue value = NifValue(); + //! The values stored from a single array copy + std::vector values; }; // The global NifTreeView clipboard pointer From 12d9bfd09fde62623dd0a6bb341b8ff352c0727e Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 28 Nov 2017 18:12:20 -0500 Subject: [PATCH 103/152] [UI] Link Array sorting Allow Ctrl-Up/Down to sort the values in arrays of links (Ptr/Ref). --- src/ui/widgets/nifview.cpp | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/ui/widgets/nifview.cpp b/src/ui/widgets/nifview.cpp index 0bf8a26e8..5f3d75c54 100644 --- a/src/ui/widgets/nifview.cpp +++ b/src/ui/widgets/nifview.cpp @@ -352,6 +352,7 @@ void NifTreeView::keyPressEvent( QKeyEvent * e ) QModelIndexList valueColumns = valueIndexList( selectedRows ); auto firstRow = selectedRows.at( 0 ); auto firstValue = valueColumns.at( 0 ); + auto firstRowType = nif->getValue( firstRow ).type(); if ( e->matches( QKeySequence::Copy ) ) { copy(); @@ -368,6 +369,39 @@ void NifTreeView::keyPressEvent( QKeyEvent * e ) paste(); } return; + } else if ( valueColumns.size() == 1 + && firstRow.parent().isValid() && nif->isArray( firstRow.parent() ) + && (firstRowType == NifValue::tUpLink || firstRowType == NifValue::tLink) ) { + // Link Array Sorting + auto parent = firstRow.parent(); + auto row = firstRow.row(); + enum { + MOVE_UP = -1, + MOVE_NONE = 0, + MOVE_DOWN = 1 + } moveDir = MOVE_NONE; + + if ( e->key() == Qt::Key_Down && e->modifiers() == Qt::CTRL && row < nif->rowCount( parent ) - 1 ) + moveDir = MOVE_DOWN; + else if ( e->key() == Qt::Key_Up && e->modifiers() == Qt::CTRL && row > 0 ) + moveDir = MOVE_UP; + + if ( moveDir ) { + // Swap the rows + row = row + moveDir; + QModelIndex newValue = firstRow.sibling( row, NifModel::ValueCol ); + QVariant v = nif->data( firstValue, Qt::EditRole ); + nif->setData( firstValue, nif->data( newValue, Qt::EditRole ) ); + nif->setData( newValue, v ); + + // Change the selected row + selectionModel()->select( parent.child( row, 0 ), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows ); + + // Add row swap to undo + ChangeValueCommand::createTransaction(); + nif->undoStack->push( new ChangeValueCommand( firstValue, nif->getValue( newValue ), nif->getValue( firstValue ), "Link", nif ) ); + nif->undoStack->push( new ChangeValueCommand( newValue, nif->getValue( firstValue ), nif->getValue( newValue ), "Link", nif ) ); + } } } From 097489c12a8f108c17a32c29d1355d21fdbdbcf5 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 28 Nov 2017 21:01:31 -0500 Subject: [PATCH 104/152] [Build] Correct subdir copy for unix Also cleans the subdirs in the process so no files linger unintentionally. Previously, shaders folder was copying into shaders/shaders on Linux if the shaders folder was already there, so shaders were not being found. Deleting the subdir first assures this does not happen. --- NifSkope_functions.pri | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/NifSkope_functions.pri b/NifSkope_functions.pri index 9c4e1fb5b..430306336 100644 --- a/NifSkope_functions.pri +++ b/NifSkope_functions.pri @@ -257,6 +257,12 @@ defineTest(copyDirs) { dirabs = $$syspath($${dirabs}) + # Fix copy for subdir on unix, also assure clean subdirs (no extra files) + !isEmpty(subdir) { + win32:QMAKE_POST_LINK += rd /s /q $${ddir} $$nt + unix:QMAKE_POST_LINK += rm -rf $${ddir} $$nt + } + QMAKE_POST_LINK += $$QMAKE_COPY_DIR $${dirabs} $${ddir} $$nt } From c22c4d2d486f481745e9436f08e7425804a87c94 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 28 Nov 2017 21:42:37 -0500 Subject: [PATCH 105/152] [GL] New texture loader New texture loading using GLI. Supports DX10 headers and BC4-BC7. Much faster than previous loader. Legacy loader is completely disabled for now and no code has been removed. Cubemap loading was consolidated under GLI as well using the same functions instead of split code paths. Added TexCache::isSupported() to reject unknown file extensions immediately. This is then stored in Tex::id before being added to the cache so that the unsupported files are ignored without string checks. Replaced gli::load_dds with a more stable function that does not crash on invalid textures. A message box will warn users of unsupported or corrupt textures. --- res/shaders/fo4_default.frag | 2 + res/shaders/fo4_effectshader.frag | 3 +- res/shaders/fo4_env.frag | 2 + src/gl/glproperty.cpp | 2 +- src/gl/glscene.cpp | 8 - src/gl/glscene.h | 2 - src/gl/gltex.cpp | 98 +++----- src/gl/gltex.h | 21 +- src/gl/gltexloaders.cpp | 395 +++++++++++++++++++++++++----- src/gl/gltexloaders.h | 16 +- 10 files changed, 388 insertions(+), 161 deletions(-) diff --git a/res/shaders/fo4_default.frag b/res/shaders/fo4_default.frag index bcc9724fe..1c4443077 100644 --- a/res/shaders/fo4_default.frag +++ b/res/shaders/fo4_default.frag @@ -209,6 +209,8 @@ void main( void ) vec4 glowMap = texture2D( GlowMap, offset ); vec3 normal = normalize(normalMap.rgb * 2.0 - 1.0); + // Calculate missing blue channel + normal.b = sqrt(1.0 - dot(normal.rg, normal.rg)); if ( !gl_FrontFacing && doubleSided ) { normal *= -1.0; } diff --git a/res/shaders/fo4_effectshader.frag b/res/shaders/fo4_effectshader.frag index 07d3fd72a..e9934bf3c 100644 --- a/res/shaders/fo4_effectshader.frag +++ b/res/shaders/fo4_effectshader.frag @@ -63,7 +63,8 @@ void main( void ) vec4 specMap = texture2D( SpecularMap, offset ); vec3 normal = normalize(normalMap.rgb * 2.0 - 1.0); - + // Calculate missing blue channel + normal.b = sqrt(1.0 - dot(normal.rg, normal.rg)); if ( !gl_FrontFacing && doubleSided ) { normal *= -1.0; } diff --git a/res/shaders/fo4_env.frag b/res/shaders/fo4_env.frag index dd1e99a2d..6c2865fcd 100644 --- a/res/shaders/fo4_env.frag +++ b/res/shaders/fo4_env.frag @@ -215,6 +215,8 @@ void main( void ) vec4 specMap = texture2D( SpecularMap, offset ); vec3 normal = normalize(normalMap.rgb * 2.0 - 1.0); + // Calculate missing blue channel + normal.b = sqrt(1.0 - dot(normal.rg, normal.rg)); if ( !gl_FrontFacing && doubleSided ) { normal *= -1.0; } diff --git a/src/gl/glproperty.cpp b/src/gl/glproperty.cpp index c5153351f..57a7285ed 100644 --- a/src/gl/glproperty.cpp +++ b/src/gl/glproperty.cpp @@ -900,7 +900,7 @@ bool BSShaderLightingProperty::bindCube( int id, const QString & fname ) GLuint result = 0; if ( !fname.isEmpty() ) - result = scene->bindTextureCube( fname ); + result = scene->bindTexture( fname ); if ( result == 0 ) return false; diff --git a/src/gl/glscene.cpp b/src/gl/glscene.cpp index 9bd9604bb..9e1d10852 100644 --- a/src/gl/glscene.cpp +++ b/src/gl/glscene.cpp @@ -476,11 +476,3 @@ int Scene::bindTexture( const QModelIndex & iSource ) return textures->bind( iSource ); } -int Scene::bindTextureCube( const QString & fname ) -{ - if ( !(options & DoTexturing) || fname.isEmpty() ) - return 0; - - return textures->bindCube( fname ); -} - diff --git a/src/gl/glscene.h b/src/gl/glscene.h index 218cbdb71..8d268174e 100644 --- a/src/gl/glscene.h +++ b/src/gl/glscene.h @@ -87,8 +87,6 @@ class Scene final : public QObject int bindTexture( const QString & fname ); int bindTexture( const QModelIndex & index ); - int bindTextureCube( const QString & fname ); - Node * getNode( const NifModel * nif, const QModelIndex & iNode ); Property * getProperty( const NifModel * nif, const QModelIndex & iProperty ); diff --git a/src/gl/gltex.cpp b/src/gl/gltex.cpp index 71df658b7..e55910949 100644 --- a/src/gl/gltex.cpp +++ b/src/gl/gltex.cpp @@ -40,6 +40,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include + #include #include #include @@ -54,8 +56,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //! @file gltex.cpp TexCache management #ifdef WIN32 -PFNGLACTIVETEXTUREARBPROC glActiveTextureARB; -PFNGLCLIENTACTIVETEXTUREARBPROC glClientActiveTextureARB; +PFNGLACTIVETEXTUREARBPROC glActiveTextureARB = nullptr; +PFNGLCLIENTACTIVETEXTUREARBPROC glClientActiveTextureARB = nullptr; #endif //! Number of texture units @@ -91,8 +93,11 @@ void initializeTextureUnits( const QOpenGLContext * context ) //qDebug() << "maximum anisotropy" << max_anisotropy; } #ifdef WIN32 - glActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC)QOpenGLContext::currentContext()->getProcAddress( "glActiveTextureARB" ); - glClientActiveTextureARB = (PFNGLCLIENTACTIVETEXTUREARBPROC)QOpenGLContext::currentContext()->getProcAddress( "glClientActiveTextureARB" ); + if ( !glActiveTextureARB ) + glActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC)SOIL_GL_GetProcAddress( "glActiveTextureARB" ); + + if ( !glClientActiveTextureARB ) + glClientActiveTextureARB = (PFNGLCLIENTACTIVETEXTUREARBPROC)SOIL_GL_GetProcAddress( "glClientActiveTextureARB" ); #endif } @@ -317,6 +322,11 @@ bool TexCache::canLoad( const QString & filePath ) return texCanLoad( filePath ); } +bool TexCache::isSupported( const QString & filePath ) +{ + return texIsSupported( filePath ); +} + void TexCache::fileChanged( const QString & filepath ) { QMutableHashIterator it( textures ); @@ -347,7 +357,6 @@ void TexCache::fileChanged( const QString & filepath ) int TexCache::bind( const QString & fname ) { Tex * tx = textures.value( fname ); - if ( !tx ) { tx = new Tex; tx->filename = fname; @@ -357,8 +366,14 @@ int TexCache::bind( const QString & fname ) tx->reload = false; textures.insert( tx->filename, tx ); + + if ( !isSupported( fname ) ) + tx->id = 0xFFFFFFFF; } + if ( tx->id == 0xFFFFFFFF ) + return 0; + QByteArray outData; if ( tx->filepath.isEmpty() || tx->reload ) @@ -369,14 +384,18 @@ int TexCache::bind( const QString & fname ) } if ( !tx->id || tx->reload ) { - if ( QFile::exists( tx->filepath ) && QFileInfo( tx->filepath ).isWritable() && ( !watcher->files().contains( tx->filepath ) ) ) + if ( QFile::exists( tx->filepath ) && QFileInfo( tx->filepath ).isWritable() + && ( !watcher->files().contains( tx->filepath ) ) ) watcher->addPath( tx->filepath ); tx->load(); } - glBindTexture( GL_TEXTURE_2D, tx->id ); - glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, get_max_anisotropy() ); + if ( !tx->target ) + tx->target = GL_TEXTURE_2D; + + glBindTexture( tx->target, tx->id ); + glTexParameterf( tx->target, GL_TEXTURE_MAX_ANISOTROPY_EXT, get_max_anisotropy() ); return tx->mipmaps; } @@ -421,43 +440,6 @@ int TexCache::bind( const QModelIndex & iSource ) return 0; } -int TexCache::bindCube( const QString & fname ) -{ - Tex * tx = textures.value( fname ); - - if ( !tx ) { - tx = new Tex; - tx->filename = fname; - tx->id = 0; - tx->data = QByteArray(); - tx->mipmaps = 0; - tx->reload = false; - - textures.insert( tx->filename, tx ); - } - - QByteArray outData; - - if ( tx->filepath.isEmpty() || tx->reload ) - tx->filepath = find( tx->filename, nifFolder, outData ); - - if ( !outData.isEmpty() ) { - tx->data = outData; - } - - if ( !tx->id || tx->reload ) { - if ( QFile::exists( tx->filepath ) && QFileInfo( tx->filepath ).isWritable() && (!watcher->files().contains( tx->filepath )) ) - watcher->addPath( tx->filepath ); - - tx->loadCube(); - } - - glBindTexture( GL_TEXTURE_CUBE_MAP, tx->id ); - glTexParameterf( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_ANISOTROPY_EXT, get_max_anisotropy() ); - - return tx->mipmaps; -} - void TexCache::flush() { for ( Tex * tx : textures ) { @@ -562,32 +544,12 @@ void TexCache::Tex::load() reload = false; status = QString(); - glBindTexture( GL_TEXTURE_2D, id ); - - try - { - texLoad( filepath, format, width, height, mipmaps, data ); - } - catch ( QString & e ) - { - status = e; - } -} - -void TexCache::Tex::loadCube() -{ - if ( !id ) - glGenTextures( 1, &id ); - - width = height = mipmaps = 0; - reload = false; - status = QString(); - - glBindTexture( GL_TEXTURE_CUBE_MAP, id ); + if ( target ) + glBindTexture( target, id ); try { - texLoadCube( filepath, format, width, height, mipmaps, data, id ); + texLoad( filepath, format, target, width, height, mipmaps, data, id ); } catch ( QString & e ) { diff --git a/src/gl/gltex.h b/src/gl/gltex.h index 6f5c42f06..9149ef86b 100644 --- a/src/gl/gltex.h +++ b/src/gl/gltex.h @@ -47,6 +47,7 @@ class QFileSystemWatcher; class QOpenGLContext; typedef unsigned int GLuint; +typedef unsigned int GLenum; /*! A class for handling OpenGL textures. * @@ -66,15 +67,17 @@ class TexCache final : public QObject //! The texture data (if not in the filesystem) QByteArray data; //! ID for use with GL texture functions - GLuint id; + GLuint id = 0; + //! The format target + GLenum target = 0; // = 0x0DE1; // GL_TEXTURE_2D //! Width of the texture - GLuint width; + GLuint width = 0; //! Height of the texture - GLuint height; + GLuint height = 0; //! Number of mipmaps present - GLuint mipmaps; + GLuint mipmaps = 0; //! Determine whether the texture needs reloading - bool reload; + bool reload = false; //! Format of the texture QString format; //! Status messages @@ -83,9 +86,6 @@ class TexCache final : public QObject //! Load the texture void load(); - //! Load the texture - void loadCube(); - //! Save the texture as a file bool saveAsFile( const QModelIndex & index, QString & savepath ); //! Save the texture as pixel data @@ -101,9 +101,6 @@ class TexCache final : public QObject //! Bind a texture from pixel data int bind( const QModelIndex & iSource ); - //! Bind a texture from filename - int bindCube( const QString & fname ); - //! Debug function for getting info about a texture QString info( const QModelIndex & iSource ); @@ -119,6 +116,8 @@ class TexCache final : public QObject static QString stripPath( const QString & file, const QString & nifFolder ); //! Checks whether the given file can be loaded static bool canLoad( const QString & file ); + //! Checks whether the extension is supported + static bool isSupported( const QString & file ); signals: void sigRefresh(); diff --git a/src/gl/gltexloaders.cpp b/src/gl/gltexloaders.cpp index 089b737ca..ed4a41e6a 100644 --- a/src/gl/gltexloaders.cpp +++ b/src/gl/gltexloaders.cpp @@ -30,6 +30,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ +#include "gli.hpp" + #include "gltexloaders.h" #include "message.h" @@ -1197,61 +1199,277 @@ GLuint texLoadNIF( QIODevice & f, QString & texformat ) return mipmaps; } +bool extInitialized = false; +bool extSupported = true; +bool extStorageSupported = true; + +// OpenGL 4.2 +PFNGLTEXSTORAGE2DPROC glTexStorage2D = nullptr; +#ifdef _WIN32 +PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC glCompressedTexSubImage2D = nullptr; +// Fallback +PFNGLCOMPRESSEDTEXIMAGE2DPROC glCompressedTexImage2D = nullptr; +#endif -bool texLoad( const QString & filepath, QString & format, GLuint & width, GLuint & height, GLuint & mipmaps ) +//! Create texture with glTexStorage2D using GLI +GLuint GLI_create_texture( gli::texture& texture, GLenum& target, GLuint& id ) { - return texLoad( filepath, format, width, height, mipmaps, *(new QByteArray()) ); + if ( !extStorageSupported ) + return 0; + + gli::gl glProfile( gli::gl::PROFILE_GL33 ); + gli::gl::format const format = glProfile.translate( texture.format(), texture.swizzles() ); + target = glProfile.translate( texture.target() ); + + if ( !id ) + glGenTextures( 1, &id ); + glBindTexture( target, id ); + glTexParameteri( target, GL_TEXTURE_BASE_LEVEL, 0 ); + glTexParameteri( target, GL_TEXTURE_MAX_LEVEL, static_cast(texture.levels() - 1) ); + glTexParameteri( target, GL_TEXTURE_SWIZZLE_R, format.Swizzles[0] ); + glTexParameteri( target, GL_TEXTURE_SWIZZLE_G, format.Swizzles[1] ); + glTexParameteri( target, GL_TEXTURE_SWIZZLE_B, format.Swizzles[2] ); + glTexParameteri( target, GL_TEXTURE_SWIZZLE_A, format.Swizzles[3] ); + + glm::tvec3 const textureExtent( texture.extent() ); + + switch ( texture.target() ) { + case gli::TARGET_2D: + case gli::TARGET_CUBE: + glTexStorage2D( target, static_cast(texture.levels()), format.Internal, + textureExtent.x, textureExtent.y + ); + break; + default: + return 0; + } + + for ( size_t layer = 0; layer < texture.layers(); ++layer ) + for ( size_t face = 0; face < texture.faces(); ++face ) + for ( size_t level = 0; level < texture.levels(); ++level ) { + GLsizei const layerGL = static_cast(layer); + glm::tvec3 textureLevelExtent( texture.extent( level ) ); + switch ( texture.target() ) { + case gli::TARGET_2D: + case gli::TARGET_CUBE: + if ( gli::is_compressed( texture.format() ) ) + glCompressedTexSubImage2D( + gli::is_target_cube( texture.target() ) ? static_cast(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face) + : target, + static_cast(level), + 0, 0, + textureLevelExtent.x, textureLevelExtent.y, + format.Internal, static_cast(texture.size( level )), + texture.data( layer, face, level ) ); + else + glTexSubImage2D( + gli::is_target_cube( texture.target() ) ? static_cast(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face) + : target, + static_cast(level), + 0, 0, + textureLevelExtent.x, textureLevelExtent.y, + format.External, format.Type, + texture.data( layer, face, level ) ); + break; + default: + return 0; + } + } + + return id; } -bool texLoad( const QString & filepath, QString & format, GLuint & width, GLuint & height, GLuint & mipmaps, QByteArray & data ) +//! Fallback for systems that do not have glTexStorage2D +GLuint GLI_create_texture_fallback( gli::texture& texture, GLenum & target, GLuint& id ) { - width = height = mipmaps = 0; + if ( texture.empty() ) + return 0; + + gli::gl GL( gli::gl::PROFILE_GL33 ); + gli::gl::format const fmt = GL.translate( texture.format(), texture.swizzles() ); + target = GL.translate( texture.target() ); + + if ( !id ) + glGenTextures( 1, &id ); + glBindTexture( target, id ); + // Base and max level are not supported by OpenGL ES 2.0 + glTexParameteri( target, GL_TEXTURE_BASE_LEVEL, 0 ); + glTexParameteri( target, GL_TEXTURE_MAX_LEVEL, static_cast(texture.levels() - 1) ); + // Texture swizzle is not supported by OpenGL ES 2.0 and OpenGL 3.2 + glTexParameteri( target, GL_TEXTURE_SWIZZLE_R, fmt.Swizzles[0] ); + glTexParameteri( target, GL_TEXTURE_SWIZZLE_G, fmt.Swizzles[1] ); + glTexParameteri( target, GL_TEXTURE_SWIZZLE_B, fmt.Swizzles[2] ); + glTexParameteri( target, GL_TEXTURE_SWIZZLE_A, fmt.Swizzles[3] ); + + for ( std::size_t layer = 0; layer < texture.layers(); ++layer ) + for ( std::size_t face = 0; face < texture.faces(); ++face ) + for ( std::size_t level = 0; level < texture.levels(); ++level ) { + GLsizei const layerGL = static_cast(layer); + glm::tvec3 extent( texture.extent( level ) ); + switch ( texture.target() ) { + case gli::TARGET_2D: + case gli::TARGET_CUBE: + if ( gli::is_compressed( texture.format() ) ) + glCompressedTexImage2D( + gli::is_target_cube( texture.target() ) ? static_cast(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face) + : target, + static_cast(level), + fmt.Internal, + extent.x, extent.y, + 0, + static_cast(texture.size( level )), + texture.data( layer, face, level ) ); + else + glTexImage2D( + gli::is_target_cube( texture.target() ) ? static_cast(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face) + : target, + static_cast(level), + fmt.Internal, + extent.x, extent.y, + 0, + fmt.External, fmt.Type, + texture.data( layer, face, level ) ); + break; + default: + return 0; + } + } + return id; +} - if ( data.isEmpty() ) { - QFile tmpF( filepath ); +//! Rewrite of gli::load_dds to not crash on invalid textures +gli::texture load_if_valid( const char * data, int size ) +{ + using namespace gli; - if ( !tmpF.open( QIODevice::ReadOnly ) ) - throw QString( "could not open file" ); + if ( strncmp( data, gli::detail::FOURCC_DDS, 4 ) != 0 || size < sizeof( gli::detail::dds_header ) ) + return texture(); - data = tmpF.readAll(); + std::size_t Offset = sizeof( gli::detail::FOURCC_DDS ); - tmpF.close(); + gli::detail::dds_header const & Header( *reinterpret_cast(data + Offset) ); + Offset += sizeof( gli::detail::dds_header ); - if ( data.isEmpty() ) - return false; + gli::detail::dds_header10 Header10; + if ( (Header.Format.flags & DDPF_FOURCC) && (Header.Format.fourCC == dx::D3DFMT_DX10 || Header.Format.fourCC == dx::D3DFMT_GLI1) ) { + std::memcpy( &Header10, data + Offset, sizeof( Header10 ) ); + Offset += sizeof( gli::detail::dds_header10 ); } - QBuffer f( &data ); + dx DX; - if ( !f.open( QIODevice::ReadOnly ) ) - throw QString( "could not open buffer" ); + gli::format Format( static_cast(gli::FORMAT_INVALID) ); + if ( (Header.Format.flags & (dx::DDPF_RGB | dx::DDPF_ALPHAPIXELS | dx::DDPF_ALPHA | dx::DDPF_YUV | dx::DDPF_LUMINANCE)) && Format == static_cast(gli::FORMAT_INVALID) && Header.Format.bpp != 0 ) { + switch ( Header.Format.bpp ) { + default: + break; + case 8: + { + if ( glm::all( glm::equal( Header.Format.Mask, DX.translate( FORMAT_RG4_UNORM_PACK8 ).Mask ) ) ) + Format = FORMAT_RG4_UNORM_PACK8; + else if ( glm::all( glm::equal( Header.Format.Mask, DX.translate( FORMAT_L8_UNORM_PACK8 ).Mask ) ) ) + Format = FORMAT_L8_UNORM_PACK8; + else if ( glm::all( glm::equal( Header.Format.Mask, DX.translate( FORMAT_A8_UNORM_PACK8 ).Mask ) ) ) + Format = FORMAT_A8_UNORM_PACK8; + else if ( glm::all( glm::equal( Header.Format.Mask, DX.translate( FORMAT_R8_UNORM_PACK8 ).Mask ) ) ) + Format = FORMAT_R8_UNORM_PACK8; + else if ( glm::all( glm::equal( Header.Format.Mask, DX.translate( FORMAT_RG3B2_UNORM_PACK8 ).Mask ) ) ) + Format = FORMAT_RG3B2_UNORM_PACK8; + break; + } + case 16: + { + if ( glm::all( glm::equal( Header.Format.Mask, DX.translate( FORMAT_RGBA4_UNORM_PACK16 ).Mask ) ) ) + Format = FORMAT_RGBA4_UNORM_PACK16; + else if ( glm::all( glm::equal( Header.Format.Mask, DX.translate( FORMAT_BGRA4_UNORM_PACK16 ).Mask ) ) ) + Format = FORMAT_BGRA4_UNORM_PACK16; + else if ( glm::all( glm::equal( Header.Format.Mask, DX.translate( FORMAT_R5G6B5_UNORM_PACK16 ).Mask ) ) ) + Format = FORMAT_R5G6B5_UNORM_PACK16; + else if ( glm::all( glm::equal( Header.Format.Mask, DX.translate( FORMAT_B5G6R5_UNORM_PACK16 ).Mask ) ) ) + Format = FORMAT_B5G6R5_UNORM_PACK16; + else if ( glm::all( glm::equal( Header.Format.Mask, DX.translate( FORMAT_RGB5A1_UNORM_PACK16 ).Mask ) ) ) + Format = FORMAT_RGB5A1_UNORM_PACK16; + else if ( glm::all( glm::equal( Header.Format.Mask, DX.translate( FORMAT_BGR5A1_UNORM_PACK16 ).Mask ) ) ) + Format = FORMAT_BGR5A1_UNORM_PACK16; + else if ( glm::all( glm::equal( Header.Format.Mask, DX.translate( FORMAT_LA8_UNORM_PACK8 ).Mask ) ) ) + Format = FORMAT_LA8_UNORM_PACK8; + else if ( glm::all( glm::equal( Header.Format.Mask, DX.translate( FORMAT_RG8_UNORM_PACK8 ).Mask ) ) ) + Format = FORMAT_RG8_UNORM_PACK8; + else if ( glm::all( glm::equal( Header.Format.Mask, DX.translate( FORMAT_L16_UNORM_PACK16 ).Mask ) ) ) + Format = FORMAT_L16_UNORM_PACK16; + else if ( glm::all( glm::equal( Header.Format.Mask, DX.translate( FORMAT_A16_UNORM_PACK16 ).Mask ) ) ) + Format = FORMAT_A16_UNORM_PACK16; + else if ( glm::all( glm::equal( Header.Format.Mask, DX.translate( FORMAT_R16_UNORM_PACK16 ).Mask ) ) ) + Format = FORMAT_R16_UNORM_PACK16; + break; + } + case 24: + { + if ( glm::all( glm::equal( Header.Format.Mask, DX.translate( FORMAT_RGB8_UNORM_PACK8 ).Mask ) ) ) + Format = FORMAT_RGB8_UNORM_PACK8; + else if ( glm::all( glm::equal( Header.Format.Mask, DX.translate( FORMAT_BGR8_UNORM_PACK8 ).Mask ) ) ) + Format = FORMAT_BGR8_UNORM_PACK8; + break; + } + case 32: + { + if ( glm::all( glm::equal( Header.Format.Mask, DX.translate( FORMAT_BGR8_UNORM_PACK32 ).Mask ) ) ) + Format = FORMAT_BGR8_UNORM_PACK32; + else if ( glm::all( glm::equal( Header.Format.Mask, DX.translate( FORMAT_BGRA8_UNORM_PACK8 ).Mask ) ) ) + Format = FORMAT_BGRA8_UNORM_PACK8; + else if ( glm::all( glm::equal( Header.Format.Mask, DX.translate( FORMAT_RGBA8_UNORM_PACK8 ).Mask ) ) ) + Format = FORMAT_RGBA8_UNORM_PACK8; + else if ( glm::all( glm::equal( Header.Format.Mask, DX.translate( FORMAT_RGB10A2_UNORM_PACK32 ).Mask ) ) ) + Format = FORMAT_RGB10A2_UNORM_PACK32; + else if ( glm::all( glm::equal( Header.Format.Mask, DX.translate( FORMAT_LA16_UNORM_PACK16 ).Mask ) ) ) + Format = FORMAT_LA16_UNORM_PACK16; + else if ( glm::all( glm::equal( Header.Format.Mask, DX.translate( FORMAT_RG16_UNORM_PACK16 ).Mask ) ) ) + Format = FORMAT_RG16_UNORM_PACK16; + else if ( glm::all( glm::equal( Header.Format.Mask, DX.translate( FORMAT_R32_SFLOAT_PACK32 ).Mask ) ) ) + Format = FORMAT_R32_SFLOAT_PACK32; + break; + } + } + } else if ( (Header.Format.flags & DDPF_FOURCC) && (Header.Format.fourCC != dx::D3DFMT_DX10) && (Header.Format.fourCC != dx::D3DFMT_GLI1) && (Format == static_cast(gli::FORMAT_INVALID)) ) { + dx::d3dfmt const FourCC = gli::detail::remap_four_cc( Header.Format.fourCC ); + Format = DX.find( FourCC ); + } else if ( Header.Format.fourCC == dx::D3DFMT_DX10 || Header.Format.fourCC == dx::D3DFMT_GLI1 ) + Format = DX.find( Header.Format.fourCC, Header10.Format ); - if ( filepath.endsWith( ".dds", Qt::CaseInsensitive ) ) - mipmaps = texLoadDDS( f, format ); - else if ( filepath.endsWith( ".tga", Qt::CaseInsensitive ) ) - mipmaps = texLoadTGA( f, format ); - else if ( filepath.endsWith( ".bmp", Qt::CaseInsensitive ) ) - mipmaps = texLoadBMP( f, format ); - else if ( filepath.endsWith( ".nif", Qt::CaseInsensitive ) || filepath.endsWith( ".texcache", Qt::CaseInsensitive ) ) - mipmaps = texLoadNIF( f, format ); - else - throw QString( "unknown texture format" ); + if ( Format == static_cast(gli::FORMAT_INVALID) ) + return texture(); - f.close(); - data.clear(); + size_t const MipMapCount = (Header.Flags & DDSD_MIPMAPCOUNT) ? Header.MipMapLevels : 1; + size_t FaceCount = 1; + if ( Header.CubemapFlags & gli::detail::DDSCAPS2_CUBEMAP ) + FaceCount = int( glm::bitCount( Header.CubemapFlags & gli::detail::DDSCAPS2_CUBEMAP_ALLFACES ) ); - glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, (GLint *)&width ); - glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, (GLint *)&height ); + size_t DepthCount = 1; + if ( Header.CubemapFlags & gli::detail::DDSCAPS2_VOLUME ) + DepthCount = Header.Depth; - return mipmaps > 0; + texture Texture( + get_target( Header, Header10 ), Format, + texture::extent_type( Header.Width, Header.Height, DepthCount ), + std::max( Header10.ArraySize, 1 ), FaceCount, MipMapCount ); + + std::size_t const SourceSize = Offset + Texture.size(); + if ( SourceSize != size ) + return texture(); + + std::memcpy( Texture.data(), data + Offset, Texture.size() ); + + return Texture; } -bool texLoadCube( const QString & filepath, QString & format, GLuint & width, GLuint & height, - GLuint & mipmaps, QByteArray & data, GLuint id ) +bool texLoad( const QString & filepath, QString & format, GLenum & target, GLuint & width, GLuint & height, GLuint & mipmaps, GLuint & id) { - Q_UNUSED( format ); + return texLoad( filepath, format, target, width, height, mipmaps, *(new QByteArray()), id ); +} +bool texLoad( const QString & filepath, QString & format, GLenum & target, GLuint & width, GLuint & height, GLuint & mipmaps, QByteArray & data, GLuint & id ) +{ width = height = mipmaps = 0; if ( data.isEmpty() ) { @@ -1268,56 +1486,100 @@ bool texLoadCube( const QString & filepath, QString & format, GLuint & width, GL return false; } - bool success = false; - GLuint result; - QBuffer f( &data ); - - if ( !f.open( QIODevice::ReadOnly ) ) + if ( !f.open( QIODevice::ReadWrite ) ) throw QString( "could not open buffer" ); - + + bool isSupported = true; if ( filepath.endsWith( ".dds", Qt::CaseInsensitive ) ) { - result = SOIL_load_OGL_single_cubemap_from_memory( - (const unsigned char *)f.data().constData(), - f.data().size(), - SOIL_DDS_CUBEMAP_FACE_ORDER, - SOIL_LOAD_AUTO, - id, - SOIL_FLAG_MIPMAPS - ); + if ( !extInitialized ) { + glTexStorage2D = (PFNGLTEXSTORAGE2DPROC)SOIL_GL_GetProcAddress( "glTexStorage2D" ); +#ifdef _WIN32 + glCompressedTexSubImage2D = (PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC)SOIL_GL_GetProcAddress( "glCompressedTexSubImage2D" ); +#endif + if ( !glTexStorage2D || !glCompressedTexSubImage2D ) + extStorageSupported = false; - if ( result == id ) { - success = true; + extInitialized = true; + } - // Just fudge the mipmaps number - mipmaps = 6; + GLuint result = 0; + gli::texture texture; + if ( extStorageSupported ) { + texture = load_if_valid( data.constData(), data.size() ); + if ( !texture.empty() ) + result = GLI_create_texture( texture, target, id ); + } else { +#ifdef _WIN32 + glCompressedTexImage2D = (PFNGLCOMPRESSEDTEXIMAGE2DPROC)SOIL_GL_GetProcAddress( "glCompressedTexImage2D" ); +#endif + if ( !glCompressedTexImage2D ) { + // Legacy DDS loader + //mipmaps = texLoadDDS( f, format ); + } else { + texture = load_if_valid( data.constData(), data.size() ); + if ( !texture.empty() ) + result = GLI_create_texture_fallback( texture, target, id ); + } } - } else { - throw QString( "unsupported texture format" ); + + if ( result ) { + id = result; + mipmaps = (GLuint)texture.levels(); + } else { + isSupported = false; + QString file = filepath; + file.replace( '/', "\\" ); + Message::append( "One or more textures failed to load.", + QString( "'%1' is corrupt or unsupported." ).arg( file ) + ); + } + + if ( !texture.empty() ) + texture.clear(); + } - + else if ( filepath.endsWith( ".tga", Qt::CaseInsensitive ) ) + mipmaps = texLoadTGA( f, format ); + else if ( filepath.endsWith( ".bmp", Qt::CaseInsensitive ) ) + mipmaps = texLoadBMP( f, format ); + else if ( filepath.endsWith( ".nif", Qt::CaseInsensitive ) || filepath.endsWith( ".texcache", Qt::CaseInsensitive ) ) + mipmaps = texLoadNIF( f, format ); + else + isSupported = false; f.close(); data.clear(); - glGetTexLevelParameteriv( GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_TEXTURE_WIDTH, (GLint *)&width ); - glGetTexLevelParameteriv( GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_TEXTURE_HEIGHT, (GLint *)&height ); + if ( isSupported ) { + GLenum t = target; + if ( target == GL_TEXTURE_CUBE_MAP ) + t = GL_TEXTURE_CUBE_MAP_POSITIVE_X; - return success; + glGetTexLevelParameteriv( t, 0, GL_TEXTURE_WIDTH, (GLint *)&width ); + glGetTexLevelParameteriv( t, 0, GL_TEXTURE_HEIGHT, (GLint *)&height ); + } else { + throw QString( "unknown texture format" ); + } + + return mipmaps > 0; } +bool texIsSupported( const QString & filepath ) +{ + return (filepath.endsWith( ".dds", Qt::CaseInsensitive ) + || filepath.endsWith( ".tga", Qt::CaseInsensitive ) + || filepath.endsWith( ".bmp", Qt::CaseInsensitive ) + || filepath.endsWith( ".nif", Qt::CaseInsensitive ) + || filepath.endsWith( ".texcache", Qt::CaseInsensitive ) + ); +} bool texCanLoad( const QString & filepath ) { QFileInfo i( filepath ); - return i.exists() && i.isReadable() - && ( filepath.endsWith( ".dds", Qt::CaseInsensitive ) - || filepath.endsWith( ".tga", Qt::CaseInsensitive ) - || filepath.endsWith( ".bmp", Qt::CaseInsensitive ) - || filepath.endsWith( ".nif", Qt::CaseInsensitive ) - || filepath.endsWith( ".texcache", Qt::CaseInsensitive ) - ); + return i.exists() && i.isReadable() && texIsSupported( filepath ); } @@ -1811,11 +2073,12 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) } else if ( filepath.endsWith( ".bmp", Qt::CaseInsensitive ) || filepath.endsWith( ".tga", Qt::CaseInsensitive ) ) { //qDebug() << "Copying from GL buffer"; - GLuint width, height, mipmaps; + GLuint width, height, mipmaps, id; + GLuint target = 0x0DE1; QString format; // fastest way to get parameters and ensure texture is active - if ( texLoad( filepath, format, width, height, mipmaps ) ) { + if ( texLoad( filepath, format, target, width, height, mipmaps, id ) ) { //qDebug() << "Width" << width << "height" << height << "mipmaps" << mipmaps << "format" << format; } else { qCCritical( nsIo ) << QObject::tr( "Error importing %1" ).arg( filepath ); diff --git a/src/gl/gltexloaders.h b/src/gl/gltexloaders.h index f6d65f64c..37cfe38a6 100644 --- a/src/gl/gltexloaders.h +++ b/src/gl/gltexloaders.h @@ -40,6 +40,7 @@ class QModelIndex; class QString; typedef unsigned int GLuint; +typedef unsigned int GLenum; //! @file gltexloaders.h Texture loading functions header @@ -57,10 +58,8 @@ typedef unsigned int GLuint; * @param mipmaps Contains the number of mipmaps on successful load. * @return True if the load was successful, false otherwise. */ -extern bool texLoad( const QString & filepath, QString & format, GLuint & width, GLuint & height, GLuint & mipmaps ); -extern bool texLoad( const QString & filepath, QString & format, GLuint & width, GLuint & height, GLuint & mipmaps, QByteArray & data ); - -extern bool texLoadCube( const QString & filepath, QString & format, GLuint & width, GLuint & height, GLuint & mipmaps, QByteArray & data, GLuint id ); +extern bool texLoad( const QString & filepath, QString & format, GLenum & target, GLuint & width, GLuint & height, GLuint & mipmaps, GLuint & id ); +extern bool texLoad( const QString & filepath, QString & format, GLenum & target, GLuint & width, GLuint & height, GLuint & mipmaps, QByteArray & data, GLuint & id ); /*! A function for loading textures. * @@ -87,6 +86,15 @@ extern bool texLoad( const QModelIndex & iData, QString & format, GLuint & width */ extern bool texCanLoad( const QString & filepath ); +/*! A function which checks whether the given file is supported. +* +* The function checks whether its extension +* is that of a supported file format (dds, tga, or bmp). +* +* @param filepath The full path to the texture that must be checked. +*/ +extern bool texIsSupported( const QString & filepath ); + /*! Save pixel data to a DDS file * * @param index Reference to pixel data From 310eb6efb645ddc361ee2f76f5e6c08575745b2d Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 28 Nov 2017 21:58:14 -0500 Subject: [PATCH 106/152] [GL] Shader improvements for FO4 For BSESP, always have vertex colors on regardless of flags, like the game. Support RGB Falloff shading. Modulate smoothness by scalar from NIF/BGSM even if specular is off (since diffuse equation now uses smoothness). --- res/shaders/fo4_default.frag | 8 +++----- res/shaders/fo4_effectshader.frag | 11 +++++++---- res/shaders/fo4_env.frag | 8 +++----- src/gl/bsshape.cpp | 4 +++- src/gl/glproperty.cpp | 8 ++++++-- src/gl/glproperty.h | 9 ++++----- src/gl/renderer.cpp | 4 +--- 7 files changed, 27 insertions(+), 25 deletions(-) diff --git a/res/shaders/fo4_default.frag b/res/shaders/fo4_default.frag index 1c4443077..76da4b9cc 100644 --- a/res/shaders/fo4_default.frag +++ b/res/shaders/fo4_default.frag @@ -251,15 +251,13 @@ void main( void ) // Specular float g = 1.0; float s = 1.0; - float smoothness = 1.0; - float roughness = 0.0; + float smoothness = clamp( specGlossiness, 0.0, 1.0 ); float specMask = 1.0; vec3 spec = vec3(0.0); if ( hasSpecularMap ) { g = specMap.g; s = specMap.r; - smoothness = g * specGlossiness; - roughness = 1.0 - smoothness; + smoothness = g * smoothness; float fSpecularPower = exp2( smoothness * 10 + 1 ); specMask = s * specStrength; @@ -282,7 +280,7 @@ void main( void ) } // Diffuse - float diff = OrenNayarFull( L, V, normal, roughness, NdotL ); + float diff = OrenNayarFull( L, V, normal, 1.0 - smoothness, NdotL ); diffuse = vec3(diff); vec3 soft = vec3(0.0); diff --git a/res/shaders/fo4_effectshader.frag b/res/shaders/fo4_effectshader.frag index e9934bf3c..710753b40 100644 --- a/res/shaders/fo4_effectshader.frag +++ b/res/shaders/fo4_effectshader.frag @@ -18,8 +18,7 @@ uniform bool greyscaleAlpha; uniform bool greyscaleColor; uniform bool useFalloff; -uniform bool vertexColors; -uniform bool vertexAlpha; +uniform bool hasRGBFalloff; uniform bool hasWeaponBlood; @@ -93,11 +92,15 @@ void main( void ) // Falloff float falloff = 1.0; - if ( useFalloff ) { + if ( useFalloff || hasRGBFalloff ) { falloff = smoothstep( falloffParams.x, falloffParams.y, abs(dot(normal, V)) ); falloff = mix( max(falloffParams.z, 0.0), min(falloffParams.w, 1.0), falloff ); - baseMap.a *= falloff; + if ( useFalloff ) + baseMap.a *= falloff; + + if ( hasRGBFalloff ) + baseMap.rgb *= falloff; } float alphaMult = baseColor.a * baseColor.a; diff --git a/res/shaders/fo4_env.frag b/res/shaders/fo4_env.frag index 6c2865fcd..9d28d56f4 100644 --- a/res/shaders/fo4_env.frag +++ b/res/shaders/fo4_env.frag @@ -256,15 +256,13 @@ void main( void ) // Specular float g = 1.0; float s = 1.0; - float smoothness = 1.0; - float roughness = 0.0; + float smoothness = clamp( specGlossiness, 0.0, 1.0 ); float specMask = 1.0; vec3 spec = vec3(0.0); if ( hasSpecularMap ) { g = specMap.g; s = specMap.r; - smoothness = g * specGlossiness; - roughness = 1.0 - smoothness; + smoothness = g * smoothness; float fSpecularPower = exp2( smoothness * 10 + 1 ); specMask = s * specStrength; @@ -301,7 +299,7 @@ void main( void ) } // Diffuse - float diff = OrenNayarFull( L, V, normal, roughness, NdotL ); + float diff = OrenNayarFull( L, V, normal, 1.0 - smoothness, NdotL ); diffuse = vec3(diff); vec3 soft = vec3(0.0); diff --git a/src/gl/bsshape.cpp b/src/gl/bsshape.cpp index 4957981df..181381b6e 100644 --- a/src/gl/bsshape.cpp +++ b/src/gl/bsshape.cpp @@ -375,7 +375,9 @@ void BSShape::drawShapes( NodeList * secondPass, bool presort ) glNormalPointer( GL_FLOAT, 0, transNorms.data() ); bool doVCs = (bssp && (bssp->getFlags2() & ShaderFlags::SLSF2_Vertex_Colors)); - + // Always do vertex colors for FO4 BSESP + if ( nifVersion == 130 && bsesp && colors.count() ) + doVCs = true; Color4 * c = nullptr; if ( colors.count() && (scene->options & Scene::DoVertexColors) && doVCs ) { diff --git a/src/gl/glproperty.cpp b/src/gl/glproperty.cpp index 57a7285ed..0d7fbbdf9 100644 --- a/src/gl/glproperty.cpp +++ b/src/gl/glproperty.cpp @@ -1440,8 +1440,8 @@ void BSEffectShaderProperty::updateParams( const NifModel * nif, const QModelInd setFlags1( nif->get( prop, "Shader Flags 1" ) ); setFlags2( nif->get( prop, "Shader Flags 2" ) ); - vertexAlpha = hasSF1( ShaderFlags::SLSF1_Vertex_Alpha ); - vertexColors = hasSF2( ShaderFlags::SLSF2_Vertex_Colors ); + hasVertexAlpha = hasSF1( ShaderFlags::SLSF1_Vertex_Alpha ); + hasVertexColors = hasSF2( ShaderFlags::SLSF2_Vertex_Colors ); if ( !m ) { setEmissive( nif->get( prop, "Emissive Color" ), nif->get( prop, "Emissive Multiple" ) ); @@ -1465,6 +1465,9 @@ void BSEffectShaderProperty::updateParams( const NifModel * nif, const QModelInd hasEnvMask = !nif->get( prop, "Env Mask Texture" ).isEmpty(); environmentReflection = nif->get( prop, "Environment Map Scale" ); + + // Receive Shadows -> RGB Falloff for FO4 + hasRGBFalloff = hasSF1( ShaderFlags::SF1( 1 << 8 ) ); } auto scale = nif->get( prop, "UV Scale" ); @@ -1500,6 +1503,7 @@ void BSEffectShaderProperty::updateParams( const NifModel * nif, const QModelInd greyscaleAlpha = m->bGrayscaleToPaletteAlpha; greyscaleColor = m->bGrayscaleToPaletteColor; useFalloff = m->bFalloffEnabled; + hasRGBFalloff = m->bFalloffColorEnabled; depthTest = m->bZBufferTest; depthWrite = m->bZBufferWrite; diff --git a/src/gl/glproperty.h b/src/gl/glproperty.h index 19256c692..8a541321a 100644 --- a/src/gl/glproperty.h +++ b/src/gl/glproperty.h @@ -562,6 +562,9 @@ class BSShaderLightingProperty : public Property bool getIsDoubleSided() { return isDoubleSided; } bool getIsTranslucent() { return isTranslucent; } + bool hasVertexColors = false; + bool hasVertexAlpha = false; + Material * mat() const; protected: @@ -649,8 +652,6 @@ class BSLightingShaderProperty final : public BSShaderLightingProperty Color3 getTintColor() const; void setTintColor( const Color3 & ); - bool hasVertexColors = false; - bool hasVertexAlpha = false; bool hasGlowMap = false; bool hasEmittance = false; bool hasSoftlight = false; @@ -741,13 +742,11 @@ class BSEffectShaderProperty final : public BSShaderLightingProperty bool hasNormalMap = false; bool hasEnvMask = false; bool useFalloff = false; + bool hasRGBFalloff = false; bool greyscaleColor = false; bool greyscaleAlpha = false; - bool vertexColors = false; - bool vertexAlpha = false; - bool hasWeaponBlood = false; struct Falloff diff --git a/src/gl/renderer.cpp b/src/gl/renderer.cpp index e880325d8..64d08bc78 100644 --- a/src/gl/renderer.cpp +++ b/src/gl/renderer.cpp @@ -877,9 +877,7 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & uni1i( "useFalloff", mesh->bsesp->useFalloff ); - uni1i( "vertexAlpha", mesh->bsesp->vertexAlpha ); - uni1i( "vertexColors", mesh->bsesp->vertexColors ); - + uni1i( "hasRGBFalloff", mesh->bsesp->hasRGBFalloff ); uni1i( "hasWeaponBlood", mesh->bsesp->hasWeaponBlood ); // Glow params From 0c996fe6d50fdb93b6fdc052785cdbca5fed6e4b Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 28 Nov 2017 22:28:18 -0500 Subject: [PATCH 107/152] [Imp/Ex] Create shader prop and texset on import for Skyrim --- src/lib/importex/obj.cpp | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/src/lib/importex/obj.cpp b/src/lib/importex/obj.cpp index e4ff3b169..dd70e521f 100644 --- a/src/lib/importex/obj.cpp +++ b/src/lib/importex/obj.cpp @@ -495,6 +495,7 @@ struct ObjMaterial Color3 Ka, Kd, Ks; float d, Ns; QString map_Kd; + QString map_Kn; ObjMaterial() : d( 1.0 ), Ns( 31.0 ) { @@ -542,6 +543,12 @@ static void readMtlLib( const QString & fname, QMap & omat for ( int i = 2; i < t.size(); i++ ) mtl.map_Kd += " " + t.value( i ); + } else if ( t.value( 0 ) == "map_Kn" ) { + // handle spaces in filenames + mtl.map_Kn = t.value( 1 ); + + for ( int i = 2; i < t.size(); i++ ) + mtl.map_Kn += " " + t.value( i ); } } @@ -774,8 +781,9 @@ void importObj( NifModel * nif, const QModelIndex & index ) ObjMaterial mtl = omaterials.value( it.key() ); + QModelIndex shaderProp; // add material property, for non-Skyrim versions - if ( nif->getUserVersion() < 12 ) { + if ( nif->getUserVersion2() <= 34 ) { bool newiMaterial = false; if ( iMaterial.isValid() == false || first_tri_shape == false ) { @@ -798,11 +806,29 @@ void importObj( NifModel * nif, const QModelIndex & index ) if ( newiMaterial ) // don't add property that is already there addLink( nif, iShape, "Properties", nif->getBlockNumber( iMaterial ) ); + } else { + shaderProp = nif->insertNiBlock( "BSLightingShaderProperty" ); + nif->set( shaderProp, "Glossiness", mtl.Ns ); + nif->set( shaderProp, "Specular Color", mtl.Ks ); + nif->setLink( iShape, "Shader Property", nif->getBlockNumber( shaderProp ) ); } if ( !mtl.map_Kd.isEmpty() ) { - if ( nif->getUserVersion() >= 12 ) { - // Skyrim, nothing here yet + if ( nif->getUserVersion2() > 34 && shaderProp.isValid() ) { + auto textureSet = nif->insertNiBlock( "BSShaderTextureSet" ); + nif->setLink( shaderProp, "Texture Set", nif->getBlockNumber( textureSet ) ); + + if ( nif->getUserVersion2() == 130 ) + nif->set( textureSet, "Num Textures", 10 ); + else + nif->set( textureSet, "Num Textures", 9 ); + + auto iTextures = nif->getIndex( textureSet, "Textures" ); + nif->updateArray( iTextures ); + + QVector texturePaths { mtl.map_Kd, mtl.map_Kn }; + nif->setArray( iTextures, texturePaths ); + nif->updateArray( iTextures ); } else if ( nif->getVersionNumber() >= 0x0303000D ) { //Newer versions use NiTexturingProperty and NiSourceTexture if ( iTexProp.isValid() == false || first_tri_shape == false || nif->itemType( iTexProp ) != "NiTexturingProperty" ) { @@ -907,6 +933,12 @@ void importObj( NifModel * nif, const QModelIndex & index ) nif->set( iData, "Num UV Sets", 1 ); nif->set( iData, "Vector Flags", 4097 ); nif->set( iData, "BS Vector Flags", 4097 ); + + if ( nif->getUserVersion2() > 34 ) { + nif->set( iData, "Has Vertex Colors", 1 ); + nif->updateArray( iData, "Vertex Colors" ); + } + QModelIndex iTexCo = nif->getIndex( iData, "UV Sets" ); if ( !iTexCo.isValid() ) From 6e43e296a9a54b1375f0291bec430ddb1cf69c4f Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Wed, 6 Dec 2017 02:06:17 -0500 Subject: [PATCH 108/152] [UI] Consolidate error messages on load failure Also improve information provided by the errors. --- src/model/nifmodel.cpp | 19 +++++++++++-------- src/nifskope_ui.cpp | 3 ++- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/model/nifmodel.cpp b/src/model/nifmodel.cpp index c60964779..e7b16112d 100644 --- a/src/model/nifmodel.cpp +++ b/src/model/nifmodel.cpp @@ -46,6 +46,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //! @file nifmodel.cpp The NIF data model. +const QString loadFail = NifModel::tr( "The NIF file could not be read. See Details for more information." ); + NifModel::NifModel( QObject * parent ) : BaseModel( parent ) { updateSettings(); @@ -499,18 +501,19 @@ bool NifModel::updateArrayItem( NifItem * array ) // Error handling if ( rows > 1024 * 1024 * 8 ) { - auto m = tr( "array %1 much too large. %2 bytes requested" ).arg( array->name() ).arg( rows ); + auto m = tr( "[%1] Array %2 much too large. %3 bytes requested" ).arg( getBlockNumber( array ) ) + .arg( array->name() ).arg( rows ); if ( msgMode == UserMessage ) { - Message::append( nullptr, tr( "Could not update array item." ), m, QMessageBox::Critical ); + Message::append( nullptr, loadFail, m, QMessageBox::Critical ); } else { testMsg( m ); } return false; } else if ( rows < 0 ) { - auto m = tr( "array %1 invalid" ).arg( array->name() ); + auto m = tr( "[%1] Array %2 invalid" ).arg( getBlockNumber( array ) ).arg( array->name() ); if ( msgMode == UserMessage ) { - Message::append( nullptr, tr( "Could not update array item." ), m, QMessageBox::Critical ); + Message::append( nullptr, loadFail, m, QMessageBox::Critical ); } else { testMsg( m ); } @@ -1801,7 +1804,7 @@ bool NifModel::load( QIODevice & device ) device.read( (char *)&len, 4 ); if ( len < 2 || len > 80 ) - throw tr( "next block does not start with a NiString" ); + throw tr( "next block (%1) does not start with a NiString" ).arg( c ); blktyp = device.read( len ); } @@ -1916,7 +1919,7 @@ bool NifModel::load( QIODevice & device ) device.read( (char *)&len, 4 ); if ( len < 0 || len > 80 ) - throw tr( "next block does not start with a NiString" ); + throw tr( "next block (%1) does not start with a NiString" ).arg( c ); QString blktyp = device.read( len ); @@ -1926,7 +1929,7 @@ bool NifModel::load( QIODevice & device ) device.read( (char *)&len, 4 ); if ( len < 0 || len > 80 ) - throw tr( "next block does not start with a NiString" ); + throw tr( "next block (%1) does not start with a NiString" ).arg( c ); blktyp = device.read( len ); } @@ -1964,7 +1967,7 @@ bool NifModel::load( QIODevice & device ) catch ( QString & err ) { if ( msgMode == UserMessage ) { - Message::critical( nullptr, tr( "The NIF file could not be read. See Details for more information." ), err ); + Message::append( nullptr, loadFail, err, QMessageBox::Critical ); } else { testMsg( err ); } diff --git a/src/nifskope_ui.cpp b/src/nifskope_ui.cpp index c363e7e89..1d7a11755 100644 --- a/src/nifskope_ui.cpp +++ b/src/nifskope_ui.cpp @@ -793,7 +793,8 @@ void NifSkope::onLoadComplete( bool success, QString & fname ) } else { // File failed to load - Message::critical( this, tr( "Failed to load %1" ).arg( fname ) ); + Message::append( this, tr( "The NIF file could not be read. See Details for more information." ), + tr( "Failed to load %1" ).arg( fname ), QMessageBox::Critical ); nif->clear(); kfm->clear(); From e0853ecd2779a9a19423f09f289993341b0ca7b5 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Wed, 6 Dec 2017 02:15:53 -0500 Subject: [PATCH 109/152] [GL] NiMesh rendering overhaul The debug code I uncommented was filled with memory leaks and was extremely slow. Larger meshes that would take 20s to render after load now render instantaneously. For one mesh, memory usage went from 1600MB to 80MB after fixing the memory leaks. Also fixed a UI issue that only appeared to affect large NiMesh NIFs during load. Errant GLPaint signals were being sent as the progress bar updated. This slowed NiMesh based NIF loading by 10-100x. --- src/data/niftypes.h | 176 ++++++++++++++++++++++++++++++ src/gl/glmesh.cpp | 253 ++++++++++++++++++++++++++------------------ src/nifskope_ui.cpp | 4 + 3 files changed, 331 insertions(+), 102 deletions(-) diff --git a/src/data/niftypes.h b/src/data/niftypes.h index f82af4915..344e5287c 100644 --- a/src/data/niftypes.h +++ b/src/data/niftypes.h @@ -1884,4 +1884,180 @@ inline QDataStream & operator>>( QDataStream & ds, BSVertexDesc & d ) return ds; } + +namespace NiMesh { + +typedef enum { + PRIMITIVE_TRIANGLES = 0, + PRIMITIVE_TRISTRIPS, + PRIMITIVE_LINES, + PRIMITIVE_LINESTRIPS, + PRIMITIVE_QUADS, + PRIMITIVE_POINTS +} PrimitiveType; + +typedef enum { + F_UNKNOWN = 0x00000000, + F_INT8_1 = 0x00010101, + F_INT8_2 = 0x00020102, + F_INT8_3 = 0x00030103, + F_INT8_4 = 0x00040104, + F_UINT8_1 = 0x00010105, + F_UINT8_2 = 0x00020106, + F_UINT8_3 = 0x00030107, + F_UINT8_4 = 0x00040108, + F_NORMINT8_1 = 0x00010109, + F_NORMINT8_2 = 0x0002010A, + F_NORMINT8_3 = 0x0003010B, + F_NORMINT8_4 = 0x0004010C, + F_NORMUINT8_1 = 0x0001010D, + F_NORMUINT8_2 = 0x0002010E, + F_NORMUINT8_3 = 0x0003010F, + F_NORMUINT8_4 = 0x00040110, + F_INT16_1 = 0x00010211, + F_INT16_2 = 0x00020212, + F_INT16_3 = 0x00030213, + F_INT16_4 = 0x00040214, + F_UINT16_1 = 0x00010215, + F_UINT16_2 = 0x00020216, + F_UINT16_3 = 0x00030217, + F_UINT16_4 = 0x00040218, + F_NORMINT16_1 = 0x00010219, + F_NORMINT16_2 = 0x0002021A, + F_NORMINT16_3 = 0x0003021B, + F_NORMINT16_4 = 0x0004021C, + F_NORMUINT16_1 = 0x0001021D, + F_NORMUINT16_2 = 0x0002021E, + F_NORMUINT16_3 = 0x0003021F, + F_NORMUINT16_4 = 0x00040220, + F_INT32_1 = 0x00010421, + F_INT32_2 = 0x00020422, + F_INT32_3 = 0x00030423, + F_INT32_4 = 0x00040424, + F_UINT32_1 = 0x00010425, + F_UINT32_2 = 0x00020426, + F_UINT32_3 = 0x00030427, + F_UINT32_4 = 0x00040428, + F_NORMINT32_1 = 0x00010429, + F_NORMINT32_2 = 0x0002042A, + F_NORMINT32_3 = 0x0003042B, + F_NORMINT32_4 = 0x0004042C, + F_NORMUINT32_1 = 0x0001042D, + F_NORMUINT32_2 = 0x0002042E, + F_NORMUINT32_3 = 0x0003042F, + F_NORMUINT32_4 = 0x00040430, + F_FLOAT16_1 = 0x00010231, + F_FLOAT16_2 = 0x00020232, + F_FLOAT16_3 = 0x00030233, + F_FLOAT16_4 = 0x00040234, + F_FLOAT32_1 = 0x00010435, + F_FLOAT32_2 = 0x00020436, + F_FLOAT32_3 = 0x00030437, + F_FLOAT32_4 = 0x00040438, + F_UINT_10_10_10_L1 = 0x00010439, + F_NORMINT_10_10_10_L1 = 0x0001043A, + F_NORMINT_11_11_10 = 0x0001043B, + F_NORMUINT8_4_BGRA = 0x0004013C, + F_NORMINT_10_10_10_2 = 0x0001043D, + F_UINT_10_10_10_2 = 0x0001043E, + F_TYPE_COUNT = 63, + F_MAX_COMP_SIZE = 16 +} DataStreamFormat; + +typedef enum +{ + // Invalid + E_Invalid = 0, + + // Vertex Semantics + E_POSITION = 1, + E_NORMAL = 2, + E_BINORMAL = 3, + E_TANGENT = 4, + E_TEXCOORD = 5, + E_BLENDWEIGHT = 6, + E_BLENDINDICES = 7, + E_COLOR = 8, + E_PSIZE = 9, + E_TESSFACTOR = 10, + E_DEPTH = 11, + E_FOG = 12, + E_POSITIONT = 13, + E_SAMPLE = 14, + E_DATASTREAM = 15, + E_INDEX = 16, + + // Skinning Semantics + E_BONEMATRICES = 17, + E_BONE_PALETTE = 18, + E_UNUSED0 = 19, + E_POSITION_BP = 20, + E_NORMAL_BP = 21, + E_BINORMAL_BP = 22, + E_TANGENT_BP = 23, + + // Morph Weights Semantic + E_MORPHWEIGHTS = 24, + + // Normal sharing semantics for use in runtime normal calculation + E_NORMALSHAREINDEX = 25, + E_NORMALSHAREGROUP = 26, + + // Instancing Semantics + E_TRANSFORMS = 27, + E_INSTANCETRANSFORMS = 28, + + // Display List Semantics + E_DISPLAYLIST = 29 +} Semantic; + +#define SEM(string) {#string, E_##string}, + +const QMap semanticStrings = { + // Vertex semantics + SEM( POSITION ) + SEM( NORMAL ) + SEM( BINORMAL ) + SEM( TANGENT ) + SEM( TEXCOORD ) + SEM( BLENDWEIGHT ) + SEM( BLENDINDICES ) + SEM( COLOR ) + SEM( PSIZE ) + SEM( TESSFACTOR ) + SEM( DEPTH ) + SEM( FOG ) + SEM( POSITIONT ) + SEM( SAMPLE ) + SEM( DATASTREAM ) + SEM( INDEX ) + + // Skinning semantics + SEM( BONEMATRICES ) + SEM( BONE_PALETTE ) + SEM( UNUSED0 ) + SEM( POSITION_BP ) + SEM( NORMAL_BP ) + SEM( BINORMAL_BP ) + SEM( TANGENT_BP ) + + // Morph weights semantics + SEM( MORPHWEIGHTS ) + + // Normal sharing semantics, for use in runtime normal calculation + SEM( NORMALSHAREINDEX ) + SEM( NORMALSHAREGROUP ) + + // Instancing Semantics + SEM( TRANSFORMS ) + SEM( INSTANCETRANSFORMS ) + + // Display list semantics + SEM( DISPLAYLIST ) +}; +#undef SEM +#undef UN + +}; + #endif diff --git a/src/gl/glmesh.cpp b/src/gl/glmesh.cpp index 2b8a37faf..3418c1edf 100644 --- a/src/gl/glmesh.cpp +++ b/src/gl/glmesh.cpp @@ -198,6 +198,9 @@ void Mesh::update( const NifModel * nif, const QModelIndex & index ) updateSkin |= ( iSkinData == index ); updateSkin |= ( iSkinPart == index ); + if ( nif->checkVersion( 0x14050000, 0 ) && nif->inherits( iBlock, "NiMesh" ) ) + updateSkin = false; + isVertexAlphaAnimation = false; isDoubleSided = false; @@ -206,14 +209,10 @@ void Mesh::update( const NifModel * nif, const QModelIndex & index ) updateShaderProperties( nif ); if ( iBlock == index ) { - // NiMesh presents a problem because we are almost guaranteed to have multiple "data" blocks - // for eg. vertices, indices, normals, texture data etc. -//#ifndef QT_NO_DEBUG if ( nif->checkVersion( 0x14050000, 0 ) && nif->inherits( iBlock, "NiMesh" ) ) { qDebug() << nif->get( iBlock, "Num Submeshes" ) << " submeshes"; iData = nif->getIndex( iBlock, "Datas" ); - if ( iData.isValid() ) { qDebug() << "Got " << nif->rowCount( iData ) << " rows of data"; updateData = true; @@ -224,8 +223,6 @@ void Mesh::update( const NifModel * nif, const QModelIndex & index ) return; } -//#endif - for ( const auto link : nif->getChildLinks( id() ) ) { QModelIndex iChild = nif->getBlock( link ); if ( !iChild.isValid() ) @@ -296,15 +293,22 @@ void Mesh::transform() if ( updateData ) { updateData = false; - // update for NiMesh + + // NiMesh Rendering if ( nif->checkVersion( 0x14050000, 0 ) && nif->inherits( iBlock, "NiMesh" ) ) { -//#ifndef QT_NO_DEBUG - // do stuff - qDebug() << "Entering NiMesh decoding..."; - // mesh primitive type - QString meshPrimitiveType = NifValue::enumOptionName( "MeshPrimitiveType", nif->get( iData, "Primitive Type" ) ); - qDebug() << "Mesh uses" << meshPrimitiveType; + verts.clear(); + norms.clear(); + tangents.clear(); + bitangents.clear(); + coords.clear(); + colors.clear(); + indices.clear(); + weights.clear(); + triangles.clear(); + + QString abortMsg = "NiMesh rendering encountered unsupported types. Rendering may be broken."; + auto meshPrimitiveType = nif->get( iBlock, "Primitive Type" ); for ( int i = 0; i < nif->rowCount( iData ); i++ ) { // each data reference is to a single data stream quint32 stream = nif->getLink( iData.child( i, 0 ), "Stream" ); @@ -319,17 +323,17 @@ void Mesh::transform() } // each stream can have multiple components, and each has a starting index - QMap componentIndexMap; + QMap> componentIndexMap; int numComponents = nif->get( iData.child( i, 0 ), "Num Components" ); qDebug() << "Components: " << numComponents; // semantics determine the usage QPersistentModelIndex componentSemantics = nif->getIndex( iData.child( i, 0 ), "Component Semantics" ); - for ( int j = 0; j < numComponents; j++ ) { - QString name = nif->get( componentSemantics.child( j, 0 ), "Name" ); + for ( uint j = 0; j < numComponents; j++ ) { + auto name = NiMesh::semanticStrings.value(nif->get( componentSemantics.child( j, 0 ), "Name" )); uint index = nif->get( componentSemantics.child( j, 0 ), "Index" ); qDebug() << "Component" << name << "at position" << index << "of component" << j << "in stream" << stream; - componentIndexMap.insert( j, QString( "%1 %2" ).arg( name ).arg( index ) ); + componentIndexMap.insert( j, {name, index} ); } // now the data stream itself... @@ -337,133 +341,178 @@ void Mesh::transform() QByteArray streamData = nif->get( nif->getIndex( dataStream, "Data" ).child( 0, 0 ) ); QBuffer streamBuffer( &streamData ); streamBuffer.open( QIODevice::ReadOnly ); - // probably won't use this - QDataStream streamReader( &streamData, QIODevice::ReadOnly ); - // we should probably check the header here, but we expect things to be little endian - streamReader.setByteOrder( QDataStream::LittleEndian ); + // each region exists within the data stream at the specified index quint32 numRegions = nif->get( dataStream, "Num Regions" ); QPersistentModelIndex regions = nif->getIndex( dataStream, "Regions" ); quint32 totalIndices = 0; - if ( regions.isValid() ) { - qDebug() << numRegions << " regions in this stream"; - - for ( quint32 j = 0; j < numRegions; j++ ) { - qDebug() << "Start index: " << nif->get( regions.child( j, 0 ), "Start Index" ); - qDebug() << "Num indices: " << nif->get( regions.child( j, 0 ), "Num Indices" ); + if ( regions.isValid() ) + for ( quint32 j = 0; j < numRegions; j++ ) totalIndices += nif->get( regions.child( j, 0 ), "Num Indices" ); - } - qDebug() << totalIndices << "total indices in" << numRegions << "regions"; - } - uint numStreamComponents = nif->get( dataStream, "Num Components" ); - qDebug() << "Stream has" << numStreamComponents << "components"; - QPersistentModelIndex streamComponents = nif->getIndex( dataStream, "Component Formats" ); // stream components are interleaved, so we need to know their type before we read them - QList typeList; + QVector typeList; + uint numStreamComponents = nif->get( dataStream, "Num Components" ); for ( uint j = 0; j < numStreamComponents; j++ ) { - uint compFormat = nif->get( streamComponents.child( j, 0 ) ); - QString compName = NifValue::enumOptionName( "ComponentFormat", compFormat ); - qDebug() << "Component format is" << compName; - qDebug() << "Stored as a" << compName.split( "_" )[1]; - typeList.append( compFormat - 1 ); - - // this can probably wait until we're reading the stream values - QString compNameIndex = componentIndexMap.value( j ); - QString compType = compNameIndex.split( " " )[0]; - uint startIndex = compNameIndex.split( " " )[1].toUInt(); - qDebug() << "Component" << j << "contains" << compType << "starting at index" << startIndex; - - // try and sort out texcoords here... - if ( compType == "TEXCOORD" ) { - QVector tempCoords; - coords.append( tempCoords ); - qDebug() << "Assigning coordinate set" << startIndex; - } + auto format = nif->get( nif->getIndex( dataStream, "Component Formats" ).child( j, 0 ) ); + typeList.append( format ); + + auto component = componentIndexMap.value( j ); + auto compType = component.first; + auto startIndex = component.second; + + // Create UV stubs for multi-coord systems + if ( compType == NiMesh::E_TEXCOORD ) + coords.append( QVector() ); } - // for each component - // get the length - // get the underlying type (will probably need OpenEXR to deal with float16 types) - // read that type in, k times, where k is the length of the vector - // start index will not be 0 if eg. multiple UV maps, but hopefully we don't have multiple components + verts.reserve( totalIndices ); + norms.reserve( totalIndices ); + tangents.reserve( totalIndices ); + bitangents.reserve( totalIndices ); + colors.reserve( totalIndices ); + indices.reserve( totalIndices ); + weights.reserve( totalIndices ); + for ( auto & c : coords ) + c.reserve( totalIndices ); + + auto tempMdl = std::make_unique( this ); + + NifIStream tempInput( tempMdl.get(), &streamBuffer ); + NifValue tempValue; + bool abort = false; for ( uint j = 0; j < totalIndices; j++ ) { for ( uint k = 0; k < numStreamComponents; k++ ) { - int typeLength = ( ( typeList[k] & 0x000F0000 ) >> 0x10 ); - int typeSize = ( ( typeList[k] & 0x00000F00 ) >> 0x08 ); - qDebug() << "Reading" << typeLength << "values" << typeSize << "bytes"; + auto typeK = typeList[k]; + int typeLength = ( (typeK & 0x000F0000) >> 0x10 ); + int typeSize = ( (typeK & 0x00000F00) >> 0x08 ); - NifIStream tempInput( new NifModel, &streamBuffer ); - QList values; - NifValue tempValue; - - // if we had the right types, we could read in Vector etc. and not have the mess below - switch ( ( typeList[k] & 0x00000FF0 ) >> 0x04 ) { + switch ( (typeK & 0x00000FF0) >> 0x04 ) { case 0x10: tempValue.changeType( NifValue::tByte ); break; + case 0x13: + if ( typeK == NiMesh::F_NORMUINT8_4_BGRA ) + tempValue.changeType( NifValue::tByteColor4 ); + typeLength = 1; + break; case 0x21: tempValue.changeType( NifValue::tShort ); + break; + case 0x23: + if ( typeLength == 3 ) + tempValue.changeType( NifValue::tHalfVector3 ); + else if ( typeLength == 2 ) + tempValue.changeType( NifValue::tHalfVector2 ); + else if ( typeLength == 1 ) + tempValue.changeType( NifValue::tHfloat ); + + typeLength = 1; + break; case 0x42: tempValue.changeType( NifValue::tInt ); break; case 0x43: - tempValue.changeType( NifValue::tFloat ); + if ( typeLength == 3 ) + tempValue.changeType( NifValue::tVector3 ); + else if ( typeLength == 2 ) + tempValue.changeType( NifValue::tVector2 ); + else if ( typeLength == 4 ) + tempValue.changeType( NifValue::tVector4 ); + else if ( typeLength == 1 ) + tempValue.changeType( NifValue::tFloat ); + + typeLength = 1; + break; } for ( int l = 0; l < typeLength; l++ ) { tempInput.read( tempValue ); - values.append( tempValue ); qDebug() << tempValue.toString(); } - QString compType = componentIndexMap.value( k ).split( " " )[0]; - qDebug() << "Will store this value in" << compType; - - // the mess begins... - if ( NifValue::enumOptionName( "ComponentFormat", (typeList[k] + 1 ) ) == "F_FLOAT32_3" ) { - Vector3 tempVect3( values[0].toFloat(), values[1].toFloat(), values[2].toFloat() ); - - if ( compType == "POSITION" || compType == "POSITION_BP" ) { - verts.append( tempVect3 ); - } else if ( compType == "NORMAL" || compType == "NORMAL_BP" ) { - norms.append( tempVect3 ); - } else if ( compType == "TANGENT" || compType == "TANGENT_BP" ) { - tangents.append( tempVect3 ); - } else if ( compType == "BINORMAL" || compType == "BINORMAL_BP" ) { - bitangents.append( tempVect3 ); + auto compType = componentIndexMap.value( k ).first; + switch ( typeK ) + { + case NiMesh::F_FLOAT32_3: + case NiMesh::F_FLOAT16_3: + switch ( compType ) { + case NiMesh::E_POSITION: + case NiMesh::E_POSITION_BP: + verts.append( tempValue.get() ); + break; + case NiMesh::E_NORMAL: + case NiMesh::E_NORMAL_BP: + norms.append( tempValue.get() ); + break; + case NiMesh::E_TANGENT: + case NiMesh::E_TANGENT_BP: + tangents.append( tempValue.get() ); + break; + case NiMesh::E_BINORMAL: + case NiMesh::E_BINORMAL_BP: + bitangents.append( tempValue.get() ); + break; } - } else if ( compType == "INDEX" ) { - indices.append( values[0].toCount() ); - } else if ( compType == "TEXCOORD" ) { - Vector2 tempVect2( values[0].toFloat(), values[1].toFloat() ); - quint32 coordSet = componentIndexMap.value( k ).split( " " )[1].toUInt(); - qDebug() << "Need to append" << tempVect2 << "to texcoords" << coordSet; - QVector currentSet = coords[coordSet]; - currentSet.append( tempVect2 ); - coords[coordSet] = currentSet; + break; + case NiMesh::F_UINT16_1: + if ( compType == NiMesh::E_INDEX ) + indices.append( tempValue.get() ); + break; + case NiMesh::F_FLOAT32_2: + case NiMesh::F_FLOAT16_2: + if ( compType == NiMesh::E_TEXCOORD ) { + quint32 coordSet = componentIndexMap.value( k ).second; + coords[coordSet].append( tempValue.get() ); + } + break; + case NiMesh::F_NORMUINT8_4_BGRA: + if ( compType == NiMesh::E_COLOR ) + colors.append( tempValue.get() ); + break; + default: + Message::append( abortMsg, QString( "[%1] Unsupported Component: %2" ).arg( stream ) + .arg( NifValue::enumOptionName( "ComponentFormat", typeK ) ), + QMessageBox::Critical ); + abort = true; + break; } + + if ( abort == true ) + break; } } - // build triangles, strips etc. - if ( meshPrimitiveType == "MESH_PRIMITIVE_TRIANGLES" ) { - for ( int k = 0; k < indices.size(); ) { - Triangle tempTri( indices[k], indices[k + 1], indices[k + 2] ); - qDebug() << "Inserting triangle" << tempTri; - triangles.append( tempTri ); - k = k + 3; - } + // Clear is extremely expensive. Must be outside of loop + tempMdl->clear(); + + // Make geometry + triangles.reserve( indices.size() / 3 ); + switch ( meshPrimitiveType ) + { + case NiMesh::PRIMITIVE_TRIANGLES: + for ( int k = 0; k < indices.size(); k += 3 ) + triangles.append( { indices[k], indices[k + 1], indices[k + 2] } ); + break; + case NiMesh::PRIMITIVE_TRISTRIPS: + case NiMesh::PRIMITIVE_LINES: + case NiMesh::PRIMITIVE_LINESTRIPS: + case NiMesh::PRIMITIVE_QUADS: + case NiMesh::PRIMITIVE_POINTS: + Message::append( abortMsg, + QString( "[%1] Unsupported Primitive: %2" ) + .arg( nif->getBlockNumber( iBlock ) ) + .arg( NifValue::enumOptionName( "MeshPrimitiveType", meshPrimitiveType ) ), + QMessageBox::Critical ); + break; } } - -//#endif } else { verts = nif->getArray( iData, "Vertices" ); diff --git a/src/nifskope_ui.cpp b/src/nifskope_ui.cpp index 1d7a11755..5d3d3a8ad 100644 --- a/src/nifskope_ui.cpp +++ b/src/nifskope_ui.cpp @@ -751,6 +751,8 @@ void NifSkope::onLoadBegin() // Disconnect the models from the views swapModels(); + ogl->setUpdatesEnabled( false ); + ogl->setEnabled( false ); setEnabled( false ); ui->tAnim->setEnabled( false ); @@ -777,6 +779,8 @@ void NifSkope::onLoadComplete( bool success, QString & fname ) swapModels(); // Re-enable window + ogl->setUpdatesEnabled( true ); + ogl->setEnabled( true ); setEnabled( true ); // IMPORTANT! int timeout = 2500; From 89b04f7c8a7c90bfb5cf7422df828df677bbb8c2 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Wed, 6 Dec 2017 02:31:10 -0500 Subject: [PATCH 110/152] [GL] Fix rendering issues glLightModeli appeared to be causing GPU panic in NiMesh NIFs when zooming in. FPS would plummet and GPU usage would be at 99.9%. get_max_anistropy() was inadvertently being called every frame for every mesh for every texture. This was causing massive slowdowns in large NIFs with thousands of blocks. pow() in setSamples() was reversed so samples was always artificially high past 0 or 1. After changing to Qt5 OpenGL, DrawTriangleIndex no longer works but the call causes major slowdowns when used. Removed index lookups for names that no longer exist and short circuit the draw functions when the rest of the block is Bethesda-only for non-Bethesda NIFs. --- src/gl/glmesh.cpp | 1 + src/gl/glnode.cpp | 28 ++++++++++++---------------- src/gl/gltex.cpp | 19 +++++++++---------- src/gl/gltex.h | 2 -- src/glview.cpp | 5 +---- 5 files changed, 23 insertions(+), 32 deletions(-) diff --git a/src/gl/glmesh.cpp b/src/gl/glmesh.cpp index 3418c1edf..ae9e64397 100644 --- a/src/gl/glmesh.cpp +++ b/src/gl/glmesh.cpp @@ -291,6 +291,7 @@ void Mesh::transform() } if ( updateData ) { + nifVersion = nif->getUserVersion2(); updateData = false; diff --git a/src/gl/glnode.cpp b/src/gl/glnode.cpp index a97a30819..a42585c2d 100644 --- a/src/gl/glnode.cpp +++ b/src/gl/glnode.cpp @@ -496,12 +496,8 @@ void Node::transform() // Scale up for Skyrim float havokScale = (nif->checkVersion( 0x14020007, 0x14020007 ) && nif->getUserVersion() >= 12) ? 10.0f : 1.0f; - QModelIndex iObject = nif->getBlock( nif->getLink( iBlock, "Collision Data" ) ); - - if ( !iObject.isValid() ) - iObject = nif->getBlock( nif->getLink( iBlock, "Collision Object" ) ); - - if ( iObject.isValid() ) { + QModelIndex iObject = nif->getBlock( nif->getLink( iBlock, "Collision Object" ) ); + if ( nif->getUserVersion2() > 0 && iObject.isValid() ) { QModelIndex iBody = nif->getBlock( nif->getLink( iObject, "Body" ) ); if ( iBody.isValid() ) { @@ -952,12 +948,12 @@ void drawHvkShape( const NifModel * nif, const QModelIndex & iShape, QStackrowCount( iTris ); t++ ) - DrawTriangleIndex( verts, nif->get( iTris.child( t, 0 ), "Triangle" ), t ); + //for ( int t = 0; t < nif->rowCount( iTris ); t++ ) + // DrawTriangleIndex( verts, nif->get( iTris.child( t, 0 ), "Triangle" ), t ); } else if ( nif->isCompound( nif->getBlockType( scene->currentIndex ) ) ) { Triangle tri = nif->get( iTris.child( i, 0 ), "Triangle" ); DrawTriangleSelection( verts, tri ); - DrawTriangleIndex( verts, tri, i ); + //DrawTriangleIndex( verts, tri, i ); } else if ( nif->getBlockName( scene->currentIndex ) == "Normal" ) { Triangle tri = nif->get( scene->currentIndex.parent(), "Triangle" ); Vector3 triCentre = ( verts.value( tri.v1() ) + verts.value( tri.v2() ) + verts.value( tri.v3() ) ) / 3.0; @@ -995,7 +991,7 @@ void drawHvkShape( const NifModel * nif, const QModelIndex & iShape, QStackgetUserVersion2() == 0 ) + return; + // Draw BSMultiBound auto iBSMultiBound = nif->getBlock( nif->getLink( iBlock, "Multi Bound" ), "BSMultiBound" ); if ( iBSMultiBound.isValid() ) { @@ -1532,11 +1532,7 @@ void Node::drawHavok() } } - QModelIndex iObject = nif->getBlock( nif->getLink( iBlock, "Collision Data" ) ); - - if ( !iObject.isValid() ) - iObject = nif->getBlock( nif->getLink( iBlock, "Collision Object" ) ); - + QModelIndex iObject = nif->getBlock( nif->getLink( iBlock, "Collision Object" ) ); if ( !iObject.isValid() ) return; diff --git a/src/gl/gltex.cpp b/src/gl/gltex.cpp index e55910949..6fb6472cd 100644 --- a/src/gl/gltex.cpp +++ b/src/gl/gltex.cpp @@ -62,16 +62,14 @@ PFNGLCLIENTACTIVETEXTUREARBPROC glClientActiveTextureARB = nullptr; //! Number of texture units GLint num_texture_units = 0; -//! Maximum anisotropy -float max_anisotropy = 1.0f; -//! Accessor function for glProperty etc. -float get_max_anisotropy() +//! Maximum anisotropy +static float max_anisotropy = 1.0f; +void set_max_anisotropy() { - QSettings settings; - float af = settings.value( "Settings/Render/General/Anisotropic Filtering", 4.0 ).toFloat(); - - return std::min( float(pow( 2.0f, af )), max_anisotropy ); + static QSettings settings; + max_anisotropy = std::min( std::pow( settings.value( "Settings/Render/General/Anisotropic Filtering", 4.0 ).toFloat(), 2.0f ), + max_anisotropy ); } void initializeTextureUnits( const QOpenGLContext * context ) @@ -90,6 +88,7 @@ void initializeTextureUnits( const QOpenGLContext * context ) if ( context->hasExtension( "GL_EXT_texture_filter_anisotropic" ) ) { glGetFloatv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &max_anisotropy ); + set_max_anisotropy(); //qDebug() << "maximum anisotropy" << max_anisotropy; } #ifdef WIN32 @@ -395,7 +394,7 @@ int TexCache::bind( const QString & fname ) tx->target = GL_TEXTURE_2D; glBindTexture( tx->target, tx->id ); - glTexParameterf( tx->target, GL_TEXTURE_MAX_ANISOTROPY_EXT, get_max_anisotropy() ); + glTexParameterf( tx->target, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy ); return tx->mipmaps; } @@ -428,7 +427,7 @@ int TexCache::bind( const QModelIndex & iSource ) } glBindTexture( GL_TEXTURE_2D, tx->id ); - glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, get_max_anisotropy() ); + glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy ); return tx->mipmaps; } diff --git a/src/gl/gltex.h b/src/gl/gltex.h index 9149ef86b..8478b04e4 100644 --- a/src/gl/gltex.h +++ b/src/gl/gltex.h @@ -143,8 +143,6 @@ protected slots: QString nifFolder; }; -float get_max_anisotropy(); - void initializeTextureUnits( const QOpenGLContext * ); bool activateTextureUnit( int x ); diff --git a/src/glview.cpp b/src/glview.cpp index e82237f51..bd2a62c0c 100644 --- a/src/glview.cpp +++ b/src/glview.cpp @@ -138,7 +138,7 @@ GLView * GLView::create( NifSkope * window ) fmt.setSwapInterval( 1 ); fmt.setDoubleBuffer( true ); - fmt.setSamples( pow( 2, aa ) ); + fmt.setSamples( std::pow( aa, 2 ) ); fmt.setDirectRendering( true ); fmt.setRgba( true ); @@ -560,9 +560,6 @@ void GLView::paintGL() glLightfv( GL_LIGHT0, GL_DIFFUSE, mat_diff ); glLightfv( GL_LIGHT0, GL_SPECULAR, mat_diff ); glLightfv( GL_LIGHT0, GL_POSITION, lightDir.data() ); - - // Necessary? - glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE ); } else { float amb = 0.5f; if ( scene->options & Scene::DisableShaders ) { From 0885773e7459b22a39760f0a9b67e0badfcbf9a5 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Wed, 6 Dec 2017 03:25:32 -0500 Subject: [PATCH 111/152] [GL] Switch to QVector for some QList Especially for strips, QVector was a better format to keep them in and is needed for upcoming changes. --- src/gl/glmesh.h | 2 +- src/gl/glnode.h | 4 ++-- src/gl/gltools.h | 2 +- src/gl/renderer.cpp | 13 +++++++------ src/gl/renderer.h | 13 +++++++------ src/lib/importex/col.cpp | 2 +- src/lib/importex/obj.cpp | 4 ++-- src/lib/nvtristripwrapper.cpp | 10 +++++----- src/lib/nvtristripwrapper.h | 4 ++-- src/spells/normals.cpp | 2 +- src/spells/skeleton.cpp | 6 +++--- src/spells/strippify.cpp | 4 ++-- src/spells/tangentspace.cpp | 2 +- src/spells/texture.cpp | 2 +- 14 files changed, 36 insertions(+), 34 deletions(-) diff --git a/src/gl/glmesh.h b/src/gl/glmesh.h index 978f6fe29..db9c9b493 100644 --- a/src/gl/glmesh.h +++ b/src/gl/glmesh.h @@ -97,7 +97,7 @@ class Shape : public Node //! Triangles QVector triangles; //! Strip points - QList> tristrips; + QVector> tristrips; //! Sorted triangles QVector sortedTriangles; //! Triangle indices diff --git a/src/gl/glnode.h b/src/gl/glnode.h index e1275b6da..3c170e0bb 100644 --- a/src/gl/glnode.h +++ b/src/gl/glnode.h @@ -64,13 +64,13 @@ class NodeList final NodeList & operator=( const NodeList & other ); - const QList & list() const { return nodes; } + const QVector & list() const { return nodes; } void sort(); void alphaSort(); protected: - QList nodes; + QVector nodes; }; class Node : public IControllable diff --git a/src/gl/gltools.h b/src/gl/gltools.h index b3a440545..fbd8c71f5 100644 --- a/src/gl/gltools.h +++ b/src/gl/gltools.h @@ -108,7 +108,7 @@ class SkinPartition final QVector > weights; QVector triangles; - QList > tristrips; + QVector > tristrips; }; QVector sortAxes( QVector axesDots ); diff --git a/src/gl/renderer.cpp b/src/gl/renderer.cpp index 64d08bc78..dd17cf2c2 100644 --- a/src/gl/renderer.cpp +++ b/src/gl/renderer.cpp @@ -119,7 +119,7 @@ Renderer::ConditionSingle::ConditionSingle( const QString & line, bool neg ) : i } } -QModelIndex Renderer::ConditionSingle::getIndex( const NifModel * nif, const QList & iBlocks, QString blkid ) const +QModelIndex Renderer::ConditionSingle::getIndex( const NifModel * nif, const QVector & iBlocks, QString blkid ) const { QString childid; @@ -144,7 +144,7 @@ QModelIndex Renderer::ConditionSingle::getIndex( const NifModel * nif, const QLi return QModelIndex(); } -bool Renderer::ConditionSingle::eval( const NifModel * nif, const QList & iBlocks ) const +bool Renderer::ConditionSingle::eval( const NifModel * nif, const QVector & iBlocks ) const { QModelIndex iLeft = getIndex( nif, iBlocks, left ); @@ -170,7 +170,7 @@ bool Renderer::ConditionSingle::eval( const NifModel * nif, const QList & iBlocks ) const +bool Renderer::ConditionGroup::eval( const NifModel * nif, const QVector & iBlocks ) const { if ( conditions.isEmpty() ) return true; @@ -456,7 +456,7 @@ QString Renderer::setupProgram( Shape * mesh, const QString & hint ) return QString( "fixed function pipeline" ); } - QList iBlocks; + QVector iBlocks; iBlocks << mesh->index(); iBlocks << mesh->iData; for ( Property * p : props.list() ) { @@ -489,14 +489,15 @@ void Renderer::stopProgram() resetTextureUnits(); } -bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & props, const QList & iBlocks ) +bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & props, + const QVector & iBlocks, bool eval ) { const NifModel * nif = qobject_cast( mesh->index().model() ); if ( !mesh->index().isValid() || !nif ) return false; - if ( !prog->conditions.eval( nif, iBlocks ) ) + if ( eval && !prog->conditions.eval( nif, iBlocks ) ) return false; fn->glUseProgram( prog->id ); diff --git a/src/gl/renderer.h b/src/gl/renderer.h index 00d126991..4042967f6 100644 --- a/src/gl/renderer.h +++ b/src/gl/renderer.h @@ -35,6 +35,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include #include @@ -92,7 +93,7 @@ public slots: Condition() {} virtual ~Condition() {} - virtual bool eval( const NifModel * nif, const QList & iBlocks ) const = 0; + virtual bool eval( const NifModel * nif, const QVector & iBlocks ) const = 0; }; //! Condition class for single conditions @@ -101,7 +102,7 @@ public slots: public: ConditionSingle( const QString & line, bool neg = false ); - bool eval( const NifModel * nif, const QList & iBlocks ) const override final; + bool eval( const NifModel * nif, const QVector & iBlocks ) const override final; protected: QString left, right; @@ -114,7 +115,7 @@ public slots: bool invert; - QModelIndex getIndex( const NifModel * nif, const QList & iBlock, QString name ) const; + QModelIndex getIndex( const NifModel * nif, const QVector & iBlock, QString name ) const; template bool compare( T a, T b ) const; }; @@ -125,14 +126,14 @@ public slots: ConditionGroup( bool o = false ) { _or = o; } ~ConditionGroup() { qDeleteAll( conditions ); } - bool eval( const NifModel * nif, const QList & iBlocks ) const override final; + bool eval( const NifModel * nif, const QVector & iBlocks ) const override final; void addCondition( Condition * c ); bool isOrGroup() const { return _or; } protected: - QList conditions; + QVector conditions; bool _or; }; @@ -175,7 +176,7 @@ public slots: QMap shaders; QMap programs; - bool setupProgram( Program *, Shape *, const PropertyList &, const QList & iBlocks ); + bool setupProgram( Program *, Shape *, const PropertyList &, const QVector & iBlocks, bool eval = true ); void setupFixedFunction( Shape *, const PropertyList & ); struct Settings diff --git a/src/lib/importex/col.cpp b/src/lib/importex/col.cpp index ee670e6ce..c99f40b59 100644 --- a/src/lib/importex/col.cpp +++ b/src/lib/importex/col.cpp @@ -842,7 +842,7 @@ void attachNiShape ( const NifModel * nif, QDomElement parentNode, int idx ) QModelIndex iPoints = nif->getIndex( iProp, "Points" ); if ( iPoints.isValid() ) { - QList > strips; + QVector > strips; for ( int r = 0; r < nif->rowCount( iPoints ); r++ ) strips.append( nif->getArray( iPoints.child( r, 0 ) ) ); diff --git a/src/lib/importex/obj.cpp b/src/lib/importex/obj.cpp index dd70e521f..0fdef0e02 100644 --- a/src/lib/importex/obj.cpp +++ b/src/lib/importex/obj.cpp @@ -99,7 +99,7 @@ static void writeData( const NifModel * nif, const QModelIndex & iData, QTextStr QModelIndex iPoints = nif->getIndex( iData, "Points" ); if ( iPoints.isValid() ) { - QList > strips; + QVector > strips; for ( int r = 0; r < nif->rowCount( iPoints ); r++ ) strips.append( nif->getArray( iPoints.child( r, 0 ) ) ); @@ -1055,7 +1055,7 @@ void importObj( NifModel * nif, const QModelIndex & index ) nif->set( iData, "Radius", radius ); // do not stitch, because it looks better in the cs - QList > strips = stripify( triangles, false ); + QVector > strips = stripify( triangles, false ); nif->set( iData, "Num Strips", strips.count() ); nif->set( iData, "Has Points", 1 ); diff --git a/src/lib/nvtristripwrapper.cpp b/src/lib/nvtristripwrapper.cpp index 5941fb574..66a9d40d0 100644 --- a/src/lib/nvtristripwrapper.cpp +++ b/src/lib/nvtristripwrapper.cpp @@ -4,14 +4,14 @@ #include -QList > stripify( QVector triangles, bool stitch ) +QVector > stripify( QVector triangles, bool stitch ) { if ( triangles.count() <= 0 ) - return QList >(); + return QVector >(); unsigned short * data = (unsigned short *)malloc( triangles.count() * 3 * sizeof( unsigned short ) ); if ( !data ) - return QList >(); + return QVector >(); for ( int t = 0; t < triangles.count(); t++ ) { data[ t * 3 + 0 ] = triangles[t][0]; @@ -27,7 +27,7 @@ QList > stripify( QVector triangles, bool stitch ) GenerateStrips( data, triangles.count() * 3, &groups, &numGroups ); free( data ); - QList > strips; + QVector > strips; if ( !groups ) return strips; @@ -73,7 +73,7 @@ QVector triangulate( QVector strip ) return tris; } -QVector triangulate( QList > strips ) +QVector triangulate( QVector > strips ) { QVector tris; for ( const QVector& strip : strips ) { diff --git a/src/lib/nvtristripwrapper.h b/src/lib/nvtristripwrapper.h index 94dc7c2eb..7f7bdf8c7 100644 --- a/src/lib/nvtristripwrapper.h +++ b/src/lib/nvtristripwrapper.h @@ -7,8 +7,8 @@ class Triangle; -QList > stripify( QVector triangles, bool stitch = true ); +QVector > stripify( QVector triangles, bool stitch = true ); QVector triangulate( QVector strips ); -QVector triangulate( QList > strips ); +QVector triangulate( QVector > strips ); #endif diff --git a/src/spells/normals.cpp b/src/spells/normals.cpp index ebae73698..c2d32555d 100644 --- a/src/spells/normals.cpp +++ b/src/spells/normals.cpp @@ -82,7 +82,7 @@ class spFaceNormals final : public Spell QModelIndex iPoints = nif->getIndex( iData, "Points" ); if ( iPoints.isValid() ) { - QList > strips; + QVector > strips; for ( int r = 0; r < nif->rowCount( iPoints ); r++ ) strips.append( nif->getArray( iPoints.child( r, 0 ) ) ); diff --git a/src/spells/skeleton.cpp b/src/spells/skeleton.cpp index 3d80c4065..9954f05a7 100644 --- a/src/spells/skeleton.cpp +++ b/src/spells/skeleton.cpp @@ -438,7 +438,7 @@ class spSkinPartition final : public Spell triangles = nif->getArray( iData, "Triangles" ).toList(); } else if ( iShapeType == "NiTriStrips" ) { // triangulate first (code copied from strippify.cpp) - QList > strips; + QVector > strips; QModelIndex iPoints = nif->getIndex( iData, "Points" ); for ( int s = 0; s < nif->rowCount( iPoints ); s++ ) { @@ -499,7 +499,7 @@ class spSkinPartition final : public Spell partTriangles = nif->getArray( iPart, "Triangles" ); } else if ( numStrips != 0 ) { // triangulate first (code copied from strippify.cpp) - QList > strips; + QVector > strips; QModelIndex iPoints = nif->getIndex( iPart, "Strips" ); for ( int s = 0; s < nif->rowCount( iPoints ); s++ ) { @@ -855,7 +855,7 @@ class spSkinPartition final : public Spell } // stripify the triangles - QList > strips; + QVector > strips; int numTriangles = 0; if ( make_strips == true ) { diff --git a/src/spells/strippify.cpp b/src/spells/strippify.cpp index daaaad737..2f2fcb1bc 100644 --- a/src/spells/strippify.cpp +++ b/src/spells/strippify.cpp @@ -65,7 +65,7 @@ class spStrippify final : public Spell //qDebug() << "num triangles" << triangles.count() << "skipped" << skip; - QList > strips = stripify( triangles, true ); + QVector > strips = stripify( triangles, true ); if ( strips.count() <= 0 ) return idx; @@ -264,7 +264,7 @@ class spTriangulate final : public Spell if ( !iStripData.isValid() ) return idx; - QList > strips; + QVector > strips; QModelIndex iPoints = nif->getIndex( iStripData, "Points" ); diff --git a/src/spells/tangentspace.cpp b/src/spells/tangentspace.cpp index e28b25d8d..79e8fdf03 100644 --- a/src/spells/tangentspace.cpp +++ b/src/spells/tangentspace.cpp @@ -105,7 +105,7 @@ QModelIndex spTangentSpace::cast( NifModel * nif, const QModelIndex & iBlock ) QModelIndex iPoints = nif->getIndex( iData, "Points" ); if ( iPoints.isValid() ) { - QList > strips; + QVector > strips; for ( int r = 0; r < nif->rowCount( iPoints ); r++ ) strips.append( nif->getArray( iPoints.child( r, 0 ) ) ); diff --git a/src/spells/texture.cpp b/src/spells/texture.cpp index b1c4171ad..94be733fd 100644 --- a/src/spells/texture.cpp +++ b/src/spells/texture.cpp @@ -661,7 +661,7 @@ class spTextureTemplate final : public Spell QModelIndex iPoints = nif->getIndex( iData, "Points" ); if ( iPoints.isValid() ) { - QList > strips; + QVector > strips; for ( int r = 0; r < nif->rowCount( iPoints ); r++ ) strips.append( nif->getArray( iPoints.child( r, 0 ) ) ); From e78e064f61715958b299a2feed20f81fb86b4a88 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Wed, 6 Dec 2017 03:42:46 -0500 Subject: [PATCH 112/152] [GL] Remove old DDS loader Completely switched to GLI and replaced all old DDS code. Removed old DDS lib files. --- NifSkope.pro | 15 - src/gl/dds/BlockDXT.cpp | 598 ----------------- src/gl/dds/BlockDXT.h | 279 -------- src/gl/dds/Color.h | 108 ---- src/gl/dds/ColorBlock.cpp | 333 ---------- src/gl/dds/ColorBlock.h | 120 ---- src/gl/dds/Common.h | 57 -- src/gl/dds/DirectDrawSurface.cpp | 1035 ------------------------------ src/gl/dds/DirectDrawSurface.h | 188 ------ src/gl/dds/Image.cpp | 143 ----- src/gl/dds/Image.h | 109 ---- src/gl/dds/PixelFormat.h | 109 ---- src/gl/dds/Stream.cpp | 108 ---- src/gl/dds/Stream.h | 54 -- src/gl/dds/dds_api.cpp | 123 ---- src/gl/dds/dds_api.h | 95 --- src/gl/gltex.cpp | 4 +- src/gl/gltexloaders.cpp | 748 +++++---------------- src/gl/gltexloaders.h | 10 +- 19 files changed, 182 insertions(+), 4054 deletions(-) delete mode 100644 src/gl/dds/BlockDXT.cpp delete mode 100644 src/gl/dds/BlockDXT.h delete mode 100644 src/gl/dds/Color.h delete mode 100644 src/gl/dds/ColorBlock.cpp delete mode 100644 src/gl/dds/ColorBlock.h delete mode 100644 src/gl/dds/Common.h delete mode 100644 src/gl/dds/DirectDrawSurface.cpp delete mode 100644 src/gl/dds/DirectDrawSurface.h delete mode 100644 src/gl/dds/Image.cpp delete mode 100644 src/gl/dds/Image.h delete mode 100644 src/gl/dds/PixelFormat.h delete mode 100644 src/gl/dds/Stream.cpp delete mode 100644 src/gl/dds/Stream.h delete mode 100644 src/gl/dds/dds_api.cpp delete mode 100644 src/gl/dds/dds_api.h diff --git a/NifSkope.pro b/NifSkope.pro index b3126001b..e12e0c798 100644 --- a/NifSkope.pro +++ b/NifSkope.pro @@ -141,15 +141,6 @@ HEADERS += \ src/data/nifitem.h \ src/data/niftypes.h \ src/data/nifvalue.h \ - src/gl/dds/BlockDXT.h \ - src/gl/dds/Color.h \ - src/gl/dds/ColorBlock.h \ - src/gl/dds/Common.h \ - src/gl/dds/dds_api.h \ - src/gl/dds/DirectDrawSurface.h \ - src/gl/dds/Image.h \ - src/gl/dds/PixelFormat.h \ - src/gl/dds/Stream.h \ src/gl/marker/constraints.h \ src/gl/marker/furniture.h \ src/gl/bsshape.h \ @@ -216,12 +207,6 @@ HEADERS += \ SOURCES += \ src/data/niftypes.cpp \ src/data/nifvalue.cpp \ - src/gl/dds/BlockDXT.cpp \ - src/gl/dds/ColorBlock.cpp \ - src/gl/dds/dds_api.cpp \ - src/gl/dds/DirectDrawSurface.cpp \ - src/gl/dds/Image.cpp \ - src/gl/dds/Stream.cpp \ src/gl/bsshape.cpp \ src/gl/controllers.cpp \ src/gl/glcontroller.cpp \ diff --git a/src/gl/dds/BlockDXT.cpp b/src/gl/dds/BlockDXT.cpp deleted file mode 100644 index 64b0c1901..000000000 --- a/src/gl/dds/BlockDXT.cpp +++ /dev/null @@ -1,598 +0,0 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2015, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -3. The name of the NIF File Format Library and Tools project may not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -***** END LICENCE BLOCK *****/ - -/* - * This file is based on a similar file from the NVIDIA texture tools - * (http://nvidia-texture-tools.googlecode.com/) - * - * Original license from NVIDIA follows. - */ - -// Copyright NVIDIA Corporation 2007 -- Ignacio Castano -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. - -#include "Common.h" - -#include "BlockDXT.h" -#include "ColorBlock.h" -#include "Stream.h" - - -/*---------------------------------------------------------------------------- - BlockDXT1 -----------------------------------------------------------------------------*/ - -uint BlockDXT1::evaluatePalette( Color32 color_array[4] ) const -{ - // Does bit expansion before interpolation. - color_array[0].b = (col0.b << 3) | (col0.b >> 2); - color_array[0].g = (col0.g << 2) | (col0.g >> 4); - color_array[0].r = (col0.r << 3) | (col0.r >> 2); - color_array[0].a = 0xFF; - - // @@ Same as above, but faster? -// Color32 c; -// c.u = ((col0.u << 3) & 0xf8) | ((col0.u << 5) & 0xfc00) | ((col0.u << 8) & 0xf80000); -// c.u |= (c.u >> 5) & 0x070007; -// c.u |= (c.u >> 6) & 0x000300; -// color_array[0].u = c.u; - - color_array[1].r = (col1.r << 3) | (col1.r >> 2); - color_array[1].g = (col1.g << 2) | (col1.g >> 4); - color_array[1].b = (col1.b << 3) | (col1.b >> 2); - color_array[1].a = 0xFF; - - // @@ Same as above, but faster? -// c.u = ((col1.u << 3) & 0xf8) | ((col1.u << 5) & 0xfc00) | ((col1.u << 8) & 0xf80000); -// c.u |= (c.u >> 5) & 0x070007; -// c.u |= (c.u >> 6) & 0x000300; -// color_array[1].u = c.u; - - if ( col0.u > col1.u ) { - // Four-color block: derive the other two colors. - color_array[2].r = (2 * color_array[0].r + color_array[1].r) / 3; - color_array[2].g = (2 * color_array[0].g + color_array[1].g) / 3; - color_array[2].b = (2 * color_array[0].b + color_array[1].b) / 3; - color_array[2].a = 0xFF; - - color_array[3].r = (2 * color_array[1].r + color_array[0].r) / 3; - color_array[3].g = (2 * color_array[1].g + color_array[0].g) / 3; - color_array[3].b = (2 * color_array[1].b + color_array[0].b) / 3; - color_array[3].a = 0xFF; - - return 4; - } else { - // Three-color block: derive the other color. - color_array[2].r = (color_array[0].r + color_array[1].r) / 2; - color_array[2].g = (color_array[0].g + color_array[1].g) / 2; - color_array[2].b = (color_array[0].b + color_array[1].b) / 2; - color_array[2].a = 0xFF; - - // Set all components to 0 to match DXT specs. - color_array[3].r = 0x00; // color_array[2].r; - color_array[3].g = 0x00; // color_array[2].g; - color_array[3].b = 0x00; // color_array[2].b; - color_array[3].a = 0x00; - - return 3; - } -} - -// Evaluate palette assuming 3 color block. -void BlockDXT1::evaluatePalette3( Color32 color_array[4] ) const -{ - color_array[0].b = (col0.b << 3) | (col0.b >> 2); - color_array[0].g = (col0.g << 2) | (col0.g >> 4); - color_array[0].r = (col0.r << 3) | (col0.r >> 2); - color_array[0].a = 0xFF; - - color_array[1].r = (col1.r << 3) | (col1.r >> 2); - color_array[1].g = (col1.g << 2) | (col1.g >> 4); - color_array[1].b = (col1.b << 3) | (col1.b >> 2); - color_array[1].a = 0xFF; - - // Three-color block: derive the other color. - color_array[2].r = (color_array[0].r + color_array[1].r) / 2; - color_array[2].g = (color_array[0].g + color_array[1].g) / 2; - color_array[2].b = (color_array[0].b + color_array[1].b) / 2; - color_array[2].a = 0xFF; - - // Set all components to 0 to match DXT specs. - color_array[3].r = 0x00; // color_array[2].r; - color_array[3].g = 0x00; // color_array[2].g; - color_array[3].b = 0x00; // color_array[2].b; - color_array[3].a = 0x00; -} - -// Evaluate palette assuming 4 color block. -void BlockDXT1::evaluatePalette4( Color32 color_array[4] ) const -{ - color_array[0].b = (col0.b << 3) | (col0.b >> 2); - color_array[0].g = (col0.g << 2) | (col0.g >> 4); - color_array[0].r = (col0.r << 3) | (col0.r >> 2); - color_array[0].a = 0xFF; - - color_array[1].r = (col1.r << 3) | (col1.r >> 2); - color_array[1].g = (col1.g << 2) | (col1.g >> 4); - color_array[1].b = (col1.b << 3) | (col1.b >> 2); - color_array[1].a = 0xFF; - - // Four-color block: derive the other two colors. - color_array[2].r = (2 * color_array[0].r + color_array[1].r) / 3; - color_array[2].g = (2 * color_array[0].g + color_array[1].g) / 3; - color_array[2].b = (2 * color_array[0].b + color_array[1].b) / 3; - color_array[2].a = 0xFF; - - color_array[3].r = (2 * color_array[1].r + color_array[0].r) / 3; - color_array[3].g = (2 * color_array[1].g + color_array[0].g) / 3; - color_array[3].b = (2 * color_array[1].b + color_array[0].b) / 3; - color_array[3].a = 0xFF; -} - -void BlockDXT1::decodeBlock( ColorBlock * block ) const -{ - // Decode color block. - Color32 color_array[4]; - evaluatePalette( color_array ); - - // Write color block. - for ( uint j = 0; j < 4; j++ ) { - for ( uint i = 0; i < 4; i++ ) { - uint idx = ( row[j] >> (2 * i) ) & 3; - block->color( i, j ) = color_array[idx]; - } - } -} - -void BlockDXT1::setIndices( int * idx ) -{ - indices = 0; - - for ( uint i = 0; i < 16; i++ ) { - indices |= (idx[i] & 3) << (2 * i); - } -} - - -/// Flip DXT1 block vertically. -inline void BlockDXT1::flip4() -{ - dds_swap( row[0], row[3] ); - dds_swap( row[1], row[2] ); -} - -/// Flip half DXT1 block vertically. -inline void BlockDXT1::flip2() -{ - dds_swap( row[0], row[1] ); -} - - -/*---------------------------------------------------------------------------- - BlockDXT3 -----------------------------------------------------------------------------*/ - -void BlockDXT3::decodeBlock( ColorBlock * block ) const -{ - // Decode color. - color.decodeBlock( block ); - - // Decode alpha. - alpha.decodeBlock( block ); -} - -void AlphaBlockDXT3::decodeBlock( ColorBlock * block ) const -{ - block->color( 0x0 ).a = (alpha0 << 4) | alpha0; - block->color( 0x1 ).a = (alpha1 << 4) | alpha1; - block->color( 0x2 ).a = (alpha2 << 4) | alpha2; - block->color( 0x3 ).a = (alpha3 << 4) | alpha3; - block->color( 0x4 ).a = (alpha4 << 4) | alpha4; - block->color( 0x5 ).a = (alpha5 << 4) | alpha5; - block->color( 0x6 ).a = (alpha6 << 4) | alpha6; - block->color( 0x7 ).a = (alpha7 << 4) | alpha7; - block->color( 0x8 ).a = (alpha8 << 4) | alpha8; - block->color( 0x9 ).a = (alpha9 << 4) | alpha9; - block->color( 0xA ).a = (alphaA << 4) | alphaA; - block->color( 0xB ).a = (alphaB << 4) | alphaB; - block->color( 0xC ).a = (alphaC << 4) | alphaC; - block->color( 0xD ).a = (alphaD << 4) | alphaD; - block->color( 0xE ).a = (alphaE << 4) | alphaE; - block->color( 0xF ).a = (alphaF << 4) | alphaF; -} - -/// Flip DXT3 alpha block vertically. -void AlphaBlockDXT3::flip4() -{ - dds_swap( row[0], row[3] ); - dds_swap( row[1], row[2] ); -} - -/// Flip half DXT3 alpha block vertically. -void AlphaBlockDXT3::flip2() -{ - dds_swap( row[0], row[1] ); -} - -/// Flip DXT3 block vertically. -void BlockDXT3::flip4() -{ - alpha.flip4(); - color.flip4(); -} - -/// Flip half DXT3 block vertically. -void BlockDXT3::flip2() -{ - alpha.flip2(); - color.flip2(); -} - - -/*---------------------------------------------------------------------------- - BlockDXT5 -----------------------------------------------------------------------------*/ - -void AlphaBlockDXT5::evaluatePalette( uint8 alpha[8] ) const -{ - if ( alpha0() > alpha1() ) { - evaluatePalette8( alpha ); - } else { - evaluatePalette6( alpha ); - } -} - -void AlphaBlockDXT5::evaluatePalette8( uint8 alpha[8] ) const -{ - // 8-alpha block: derive the other six alphas. - // Bit code 000 = alpha0, 001 = alpha1, others are interpolated. - alpha[0] = alpha0(); - alpha[1] = alpha1(); - alpha[2] = (6 * alpha[0] + 1 * alpha[1]) / 7; // bit code 010 - alpha[3] = (5 * alpha[0] + 2 * alpha[1]) / 7; // bit code 011 - alpha[4] = (4 * alpha[0] + 3 * alpha[1]) / 7; // bit code 100 - alpha[5] = (3 * alpha[0] + 4 * alpha[1]) / 7; // bit code 101 - alpha[6] = (2 * alpha[0] + 5 * alpha[1]) / 7; // bit code 110 - alpha[7] = (1 * alpha[0] + 6 * alpha[1]) / 7; // bit code 111 -} - -void AlphaBlockDXT5::evaluatePalette6( uint8 alpha[8] ) const -{ - // 6-alpha block. - // Bit code 000 = alpha0, 001 = alpha1, others are interpolated. - alpha[0] = alpha0(); - alpha[1] = alpha1(); - alpha[2] = (4 * alpha[0] + 1 * alpha[1]) / 5; // Bit code 010 - alpha[3] = (3 * alpha[0] + 2 * alpha[1]) / 5; // Bit code 011 - alpha[4] = (2 * alpha[0] + 3 * alpha[1]) / 5; // Bit code 100 - alpha[5] = (1 * alpha[0] + 4 * alpha[1]) / 5; // Bit code 101 - alpha[6] = 0x00; // Bit code 110 - alpha[7] = 0xFF; // Bit code 111 -} - -void AlphaBlockDXT5::indices( uint8 index_array[16] ) const -{ - index_array[0x0] = bits0(); - index_array[0x1] = bits1(); - index_array[0x2] = bits2(); - index_array[0x3] = bits3(); - index_array[0x4] = bits4(); - index_array[0x5] = bits5(); - index_array[0x6] = bits6(); - index_array[0x7] = bits7(); - index_array[0x8] = bits8(); - index_array[0x9] = bits9(); - index_array[0xA] = bitsA(); - index_array[0xB] = bitsB(); - index_array[0xC] = bitsC(); - index_array[0xD] = bitsD(); - index_array[0xE] = bitsE(); - index_array[0xF] = bitsF(); -} - -uint AlphaBlockDXT5::index( uint index ) const -{ - int offset = (3 * index + 16); - return uint( (this->u >> offset) & 0x7 ); -} - -void AlphaBlockDXT5::setIndex( uint index, uint value ) -{ - int offset = (3 * index + 16); - uint64 mask = uint64( 0x7 ) << offset; - this->u = (this->u & ~mask) | (uint64( value ) << offset); -} - -void AlphaBlockDXT5::decodeBlock( ColorBlock * block ) const -{ - uint8 alpha_array[8]; - evaluatePalette( alpha_array ); - - uint8 index_array[16]; - indices( index_array ); - - for ( uint i = 0; i < 16; i++ ) { - block->color( i ).a = alpha_array[index_array[i]]; - } -} - -void AlphaBlockDXT5::flip4() -{ - uint64 * b = (uint64 *)this; - - // @@ The masks might have to be byte swapped. - uint64 tmp = ( *b & (uint64)(0x000000000000FFFFLL) ); - tmp |= ( *b & (uint64)(0x000000000FFF0000LL) ) << 36; - tmp |= ( *b & (uint64)(0x000000FFF0000000LL) ) << 12; - tmp |= ( *b & (uint64)(0x000FFF0000000000LL) ) >> 12; - tmp |= ( *b & (uint64)(0xFFF0000000000000LL) ) >> 36; - - *b = tmp; -} - -void AlphaBlockDXT5::flip2() -{ - uint * b = (uint *)this; - - // @@ The masks might have to be byte swapped. - uint tmp = (*b & 0xFF000000); - tmp |= (*b & 0x00000FFF) << 12; - tmp |= (*b & 0x00FFF000) >> 12; - - *b = tmp; -} - -void BlockDXT5::decodeBlock( ColorBlock * block ) const -{ - // Decode color. - color.decodeBlock( block ); - - // Decode alpha. - alpha.decodeBlock( block ); -} - -/// Flip DXT5 block vertically. -void BlockDXT5::flip4() -{ - alpha.flip4(); - color.flip4(); -} - -/// Flip half DXT5 block vertically. -void BlockDXT5::flip2() -{ - alpha.flip2(); - color.flip2(); -} - - -/// Decode ATI1 block. -void BlockATI1::decodeBlock( ColorBlock * block ) const -{ - uint8 alpha_array[8]; - alpha.evaluatePalette( alpha_array ); - - uint8 index_array[16]; - alpha.indices( index_array ); - - for ( uint i = 0; i < 16; i++ ) { - Color32 & c = block->color( i ); - c.b = c.g = c.r = alpha_array[index_array[i]]; - c.a = 255; - } -} - -/// Flip ATI1 block vertically. -void BlockATI1::flip4() -{ - alpha.flip4(); -} - -/// Flip half ATI1 block vertically. -void BlockATI1::flip2() -{ - alpha.flip2(); -} - - -/// Decode ATI2 block. -void BlockATI2::decodeBlock( ColorBlock * block ) const -{ - uint8 alpha_array[8]; - uint8 index_array[16]; - - x.evaluatePalette( alpha_array ); - x.indices( index_array ); - - for ( uint i = 0; i < 16; i++ ) { - Color32 & c = block->color( i ); - c.r = alpha_array[index_array[i]]; - } - - y.evaluatePalette( alpha_array ); - y.indices( index_array ); - - for ( uint i = 0; i < 16; i++ ) { - Color32 & c = block->color( i ); - c.g = alpha_array[index_array[i]]; - c.b = 0; - c.a = 255; - } -} - -/// Flip ATI2 block vertically. -void BlockATI2::flip4() -{ - x.flip4(); - y.flip4(); -} - -/// Flip half ATI2 block vertically. -void BlockATI2::flip2() -{ - x.flip2(); - y.flip2(); -} - - -void BlockCTX1::evaluatePalette( Color32 color_array[4] ) const -{ - // Does bit expansion before interpolation. - color_array[0].b = 0x00; - color_array[0].g = col0[1]; - color_array[0].r = col0[0]; - color_array[0].a = 0xFF; - - color_array[1].r = 0x00; - color_array[1].g = col0[1]; - color_array[1].b = col1[0]; - color_array[1].a = 0xFF; - - color_array[2].r = 0x00; - color_array[2].g = (2 * color_array[0].g + color_array[1].g) / 3; - color_array[2].b = (2 * color_array[0].b + color_array[1].b) / 3; - color_array[2].a = 0xFF; - - color_array[3].r = 0x00; - color_array[3].g = (2 * color_array[1].g + color_array[0].g) / 3; - color_array[3].b = (2 * color_array[1].b + color_array[0].b) / 3; - color_array[3].a = 0xFF; -} - -void BlockCTX1::decodeBlock( ColorBlock * block ) const -{ - // Decode color block. - Color32 color_array[4]; - evaluatePalette( color_array ); - - // Write color block. - for ( uint j = 0; j < 4; j++ ) { - for ( uint i = 0; i < 4; i++ ) { - uint idx = ( row[j] >> (2 * i) ) & 3; - block->color( i, j ) = color_array[idx]; - } - } -} - -void BlockCTX1::setIndices( int * idx ) -{ - indices = 0; - - for ( uint i = 0; i < 16; i++ ) { - indices |= (idx[i] & 3) << (2 * i); - } -} - - -/// Flip CTX1 block vertically. -inline void BlockCTX1::flip4() -{ - dds_swap( row[0], row[3] ); - dds_swap( row[1], row[2] ); -} - -/// Flip half CTX1 block vertically. -inline void BlockCTX1::flip2() -{ - dds_swap( row[0], row[1] ); -} - -void mem_read( Stream & mem, BlockDXT1 & block ) -{ - mem_read( mem, block.col0.u ); - mem_read( mem, block.col1.u ); - mem_read( mem, block.indices ); -} - -void mem_read( Stream & mem, AlphaBlockDXT3 & block ) -{ - for ( unsigned int i = 0; i < 4; i++ ) - mem_read( mem, block.row[i] ); -} - -void mem_read( Stream & mem, BlockDXT3 & block ) -{ - mem_read( mem, block.alpha ); - mem_read( mem, block.color ); -} - -void mem_read( Stream & mem, AlphaBlockDXT5 & block ) -{ - mem_read( mem, block.u ); -} - -void mem_read( Stream & mem, BlockDXT5 & block ) -{ - mem_read( mem, block.alpha ); - mem_read( mem, block.color ); -} - -void mem_read( Stream & mem, BlockATI1 & block ) -{ - mem_read( mem, block.alpha ); -} - -void mem_read( Stream & mem, BlockATI2 & block ) -{ - mem_read( mem, block.x ); - mem_read( mem, block.y ); -} - -void mem_read( Stream & mem, BlockCTX1 & block ) -{ - mem_read( mem, block.col0[0] ); - mem_read( mem, block.col0[1] ); - mem_read( mem, block.col1[0] ); - mem_read( mem, block.col1[1] ); - mem_read( mem, block.indices ); -} - diff --git a/src/gl/dds/BlockDXT.h b/src/gl/dds/BlockDXT.h deleted file mode 100644 index fb22a20b2..000000000 --- a/src/gl/dds/BlockDXT.h +++ /dev/null @@ -1,279 +0,0 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2015, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -3. The name of the NIF File Format Library and Tools project may not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -***** END LICENCE BLOCK *****/ - -/* - * This file is based on a similar file from the NVIDIA texture tools - * (http://nvidia-texture-tools.googlecode.com/) - * - * Original license from NVIDIA follows. - */ - -// Copyright NVIDIA Corporation 2007 -- Ignacio Castano -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. - -#ifndef _DDS_BLOCKDXT_H -#define _DDS_BLOCKDXT_H - -#include "Common.h" -#include "Color.h" -#include "ColorBlock.h" -#include "Stream.h" - -/// DXT1 block. -struct BlockDXT1 -{ - Color16 col0; - Color16 col1; - union - { - uint8 row[4]; - uint indices; - }; - - bool isFourColorMode() const; - - uint evaluatePalette( Color32 color_array[4] ) const; - uint evaluatePaletteFast( Color32 color_array[4] ) const; - void evaluatePalette3( Color32 color_array[4] ) const; - void evaluatePalette4( Color32 color_array[4] ) const; - - void decodeBlock( ColorBlock * block ) const; - - void setIndices( int * idx ); - - void flip4(); - void flip2(); -}; - -/// Return true if the block uses four color mode, false otherwise. -inline bool BlockDXT1::isFourColorMode() const -{ - return col0.u > col1.u; -} - - -/// DXT3 alpha block with explicit alpha. -struct AlphaBlockDXT3 -{ - union - { - struct - { - uint alpha0 : 4; - uint alpha1 : 4; - uint alpha2 : 4; - uint alpha3 : 4; - uint alpha4 : 4; - uint alpha5 : 4; - uint alpha6 : 4; - uint alpha7 : 4; - uint alpha8 : 4; - uint alpha9 : 4; - uint alphaA : 4; - uint alphaB : 4; - uint alphaC : 4; - uint alphaD : 4; - uint alphaE : 4; - uint alphaF : 4; - }; - uint16 row[4]; - }; - - void decodeBlock( ColorBlock * block ) const; - - void flip4(); - void flip2(); -}; - - -/// DXT3 block. -struct BlockDXT3 -{ - AlphaBlockDXT3 alpha; - BlockDXT1 color; - - void decodeBlock( ColorBlock * block ) const; - - void flip4(); - void flip2(); -}; - - -/// DXT5 alpha block. -struct AlphaBlockDXT5 -{ - // uint64 unions do not compile on all platforms - /* - union { - struct { - uint64 alpha0 : 8; // 8 - uint64 alpha1 : 8; // 16 - uint64 bits0 : 3; // 3 - 19 - uint64 bits1 : 3; // 6 - 22 - uint64 bits2 : 3; // 9 - 25 - uint64 bits3 : 3; // 12 - 28 - uint64 bits4 : 3; // 15 - 31 - uint64 bits5 : 3; // 18 - 34 - uint64 bits6 : 3; // 21 - 37 - uint64 bits7 : 3; // 24 - 40 - uint64 bits8 : 3; // 27 - 43 - uint64 bits9 : 3; // 30 - 46 - uint64 bitsA : 3; // 33 - 49 - uint64 bitsB : 3; // 36 - 52 - uint64 bitsC : 3; // 39 - 55 - uint64 bitsD : 3; // 42 - 58 - uint64 bitsE : 3; // 45 - 61 - uint64 bitsF : 3; // 48 - 64 - }; - uint64 u; - }; - */ - uint64 u; - uint8 alpha0() const { return u & 0xffLL; }; - uint8 alpha1() const { return (u >> 8) & 0xffLL; }; - uint8 bits0() const { return (u >> 16) & 0x7LL; }; - uint8 bits1() const { return (u >> 19) & 0x7LL; }; - uint8 bits2() const { return (u >> 22) & 0x7LL; }; - uint8 bits3() const { return (u >> 25) & 0x7LL; }; - uint8 bits4() const { return (u >> 28) & 0x7LL; }; - uint8 bits5() const { return (u >> 31) & 0x7LL; }; - uint8 bits6() const { return (u >> 34) & 0x7LL; }; - uint8 bits7() const { return (u >> 37) & 0x7LL; }; - uint8 bits8() const { return (u >> 40) & 0x7LL; }; - uint8 bits9() const { return (u >> 43) & 0x7LL; }; - uint8 bitsA() const { return (u >> 46) & 0x7LL; }; - uint8 bitsB() const { return (u >> 49) & 0x7LL; }; - uint8 bitsC() const { return (u >> 52) & 0x7LL; }; - uint8 bitsD() const { return (u >> 55) & 0x7LL; }; - uint8 bitsE() const { return (u >> 58) & 0x7LL; }; - uint8 bitsF() const { return (u >> 61) & 0x7LL; }; - - void evaluatePalette( uint8 alpha[8] ) const; - void evaluatePalette8( uint8 alpha[8] ) const; - void evaluatePalette6( uint8 alpha[8] ) const; - void indices( uint8 index_array[16] ) const; - - uint index( uint index ) const; - void setIndex( uint index, uint value ); - - void decodeBlock( ColorBlock * block ) const; - - void flip4(); - void flip2(); -}; - - -/// DXT5 block. -struct BlockDXT5 -{ - AlphaBlockDXT5 alpha; - BlockDXT1 color; - - void decodeBlock( ColorBlock * block ) const; - - void flip4(); - void flip2(); -}; - -/// ATI1 block. -struct BlockATI1 -{ - AlphaBlockDXT5 alpha; - - void decodeBlock( ColorBlock * block ) const; - - void flip4(); - void flip2(); -}; - -/// ATI2 block. -struct BlockATI2 -{ - AlphaBlockDXT5 x; - AlphaBlockDXT5 y; - - void decodeBlock( ColorBlock * block ) const; - - void flip4(); - void flip2(); -}; - -/// CTX1 block. -struct BlockCTX1 -{ - uint8 col0[2]; - uint8 col1[2]; - union - { - uint8 row[4]; - uint indices; - }; - - void evaluatePalette( Color32 color_array[4] ) const; - void setIndices( int * idx ); - - void decodeBlock( ColorBlock * block ) const; - - void flip4(); - void flip2(); -}; - -void mem_read( Stream & mem, BlockDXT1 & block ); -void mem_read( Stream & mem, AlphaBlockDXT3 & block ); -void mem_read( Stream & mem, BlockDXT3 & block ); -void mem_read( Stream & mem, AlphaBlockDXT5 & block ); -void mem_read( Stream & mem, BlockDXT5 & block ); -void mem_read( Stream & mem, BlockATI1 & block ); -void mem_read( Stream & mem, BlockATI2 & block ); -void mem_read( Stream & mem, BlockCTX1 & block ); - -#endif // _DDS_BLOCKDXT_H diff --git a/src/gl/dds/Color.h b/src/gl/dds/Color.h deleted file mode 100644 index 2f2482aee..000000000 --- a/src/gl/dds/Color.h +++ /dev/null @@ -1,108 +0,0 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2015, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -3. The name of the NIF File Format Library and Tools project may not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -***** END LICENCE BLOCK *****/ - -/* - * This file is based on a similar file from the NVIDIA texture tools - * (http://nvidia-texture-tools.googlecode.com/) - * - * Original license from NVIDIA follows. - */ - -// This code is in the public domain -- castanyo@yahoo.es - -#ifndef _DDS_COLOR_H -#define _DDS_COLOR_H - -/// 32 bit color stored as BGRA. -class Color32 -{ -public: - Color32() { } - Color32( const Color32 & c ) : u( c.u ) { } - Color32( unsigned char R, unsigned char G, unsigned char B ) { setRGBA( R, G, B, 0xFF ); } - Color32( unsigned char R, unsigned char G, unsigned char B, unsigned char A ) { setRGBA( R, G, B, A ); } - //Color32(unsigned char c[4]) { setRGBA(c[0], c[1], c[2], c[3]); } - //Color32(float R, float G, float B) { setRGBA(uint(R*255), uint(G*255), uint(B*255), 0xFF); } - //Color32(float R, float G, float B, float A) { setRGBA(uint(R*255), uint(G*255), uint(B*255), uint(A*255)); } - Color32( unsigned int U ) : u( U ) { } - - void setRGBA( unsigned char R, unsigned char G, unsigned char B, unsigned char A ) - { - r = R; - g = G; - b = B; - a = A; - } - - void setBGRA( unsigned char B, unsigned char G, unsigned char R, unsigned char A = 0xFF ) - { - r = R; - g = G; - b = B; - a = A; - } - - operator unsigned int() const { - return u; - } - - union - { - struct - { - unsigned char b, g, r, a; - }; - unsigned int u; - }; -}; - -/// 16 bit 565 BGR color. -class Color16 -{ -public: - Color16() { } - Color16( const Color16 & c ) : u( c.u ) { } - explicit Color16( unsigned short U ) : u( U ) { } - - union - { - struct - { - unsigned short b : 5; - unsigned short g : 6; - unsigned short r : 5; - }; - unsigned short u; - }; -}; - -#endif // _DDS_COLOR_H diff --git a/src/gl/dds/ColorBlock.cpp b/src/gl/dds/ColorBlock.cpp deleted file mode 100644 index b158882ba..000000000 --- a/src/gl/dds/ColorBlock.cpp +++ /dev/null @@ -1,333 +0,0 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2015, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -3. The name of the NIF File Format Library and Tools project may not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -***** END LICENCE BLOCK *****/ - -/* - * This file is based on a similar file from the NVIDIA texture tools - * (http://nvidia-texture-tools.googlecode.com/) - * - * Original license from NVIDIA follows. - */ - -// This code is in the public domain -- castanyo@yahoo.es - -#include "ColorBlock.h" - -#include "Common.h" -#include - -// Get approximate luminance. -inline static uint colorLuminance( Color32 c ) -{ - return c.r + c.g + c.b; -} - -// Get the euclidean distance between the given colors. -inline static uint colorDistance( Color32 c0, Color32 c1 ) -{ - return (c0.r - c1.r) * (c0.r - c1.r) + (c0.g - c1.g) * (c0.g - c1.g) + (c0.b - c1.b) * (c0.b - c1.b); -} - - -/// Default constructor. -ColorBlock::ColorBlock() -{ -} - -/// Init the color block with the contents of the given block. -ColorBlock::ColorBlock( const ColorBlock & block ) -{ - for ( uint i = 0; i < 16; i++ ) { - color( i ) = block.color( i ); - } -} - - -/// Initialize this color block. -ColorBlock::ColorBlock( const Image * img, uint x, uint y ) -{ - init( img, x, y ); -} - -void ColorBlock::init( const Image * img, uint x, uint y ) -{ - const uint bw = std::min( img->width() - x, 4U ); - const uint bh = std::min( img->height() - y, 4U ); - - static int remainder[] = { - 0, 0, 0, 0, - 0, 1, 0, 1, - 0, 1, 2, 0, - 0, 1, 2, 3, - }; - - // Blocks that are smaller than 4x4 are handled by repeating the pixels. - // @@ Thats only correct when block size is 1, 2 or 4, but not with 3. :( - - for ( uint i = 0; i < 4; i++ ) { - //const int by = i % bh; - const int by = remainder[(bh - 1) * 4 + i]; - - for ( uint e = 0; e < 4; e++ ) { - //const int bx = e % bw; - const int bx = remainder[(bw - 1) * 4 + e]; - color( e, i ) = img->pixel( x + bx, y + by ); - } - } -} - - -void ColorBlock::swizzleDXT5n() -{ - for ( int i = 0; i < 16; i++ ) { - Color32 c = m_color[i]; - m_color[i] = Color32( 0xFF, c.g, 0, c.r ); - } -} - -void ColorBlock::splatX() -{ - for ( int i = 0; i < 16; i++ ) { - uint8 x = m_color[i].r; - m_color[i] = Color32( x, x, x, x ); - } -} - -void ColorBlock::splatY() -{ - for ( int i = 0; i < 16; i++ ) { - uint8 y = m_color[i].g; - m_color[i] = Color32( y, y, y, y ); - } -} - -/// Returns true if the block has a single color. -bool ColorBlock::isSingleColor() const -{ - for ( int i = 1; i < 16; i++ ) { - if ( m_color[0] != m_color[i] ) { - return false; - } - } - - return true; -} - -/// Count number of unique colors in this color block. -uint ColorBlock::countUniqueColors() const -{ - uint count = 0; - - // @@ This does not have to be o(n^2) - for ( int i = 0; i < 16; i++ ) { - bool unique = true; - - for ( int j = 0; j < i; j++ ) { - if ( m_color[i] != m_color[j] ) { - unique = false; - } - } - - if ( unique ) { - count++; - } - } - - return count; -} - -/// Get average color of the block. -Color32 ColorBlock::averageColor() const -{ - uint r, g, b, a; - r = g = b = a = 0; - - for ( uint i = 0; i < 16; i++ ) { - r += m_color[i].r; - g += m_color[i].g; - b += m_color[i].b; - a += m_color[i].a; - } - - return Color32( uint8( r / 16 ), uint8( g / 16 ), uint8( b / 16 ), uint8( a / 16 ) ); -} - -/// Return true if the block is not fully opaque. -bool ColorBlock::hasAlpha() const -{ - for ( uint i = 0; i < 16; i++ ) { - if ( m_color[i].a != 255 ) - return true; - } - - return false; -} - - -/// Get diameter color range. -void ColorBlock::diameterRange( Color32 * start, Color32 * end ) const -{ - Color32 c0, c1; - uint best_dist = 0; - - for ( int i = 0; i < 16; i++ ) { - for ( int j = i + 1; j < 16; j++ ) { - uint dist = colorDistance( m_color[i], m_color[j] ); - - if ( dist > best_dist ) { - best_dist = dist; - c0 = m_color[i]; - c1 = m_color[j]; - } - } - } - - *start = c0; - *end = c1; -} - -/// Get luminance color range. -void ColorBlock::luminanceRange( Color32 * start, Color32 * end ) const -{ - Color32 minColor, maxColor; - uint minLuminance, maxLuminance; - - maxLuminance = minLuminance = colorLuminance( m_color[0] ); - - for ( uint i = 1; i < 16; i++ ) { - uint luminance = colorLuminance( m_color[i] ); - - if ( luminance > maxLuminance ) { - maxLuminance = luminance; - maxColor = m_color[i]; - } else if ( luminance < minLuminance ) { - minLuminance = luminance; - minColor = m_color[i]; - } - } - - *start = minColor; - *end = maxColor; -} - -/// Get color range based on the bounding box. -void ColorBlock::boundsRange( Color32 * start, Color32 * end ) const -{ - Color32 minColor( 255, 255, 255 ); - Color32 maxColor( 0, 0, 0 ); - - for ( uint i = 0; i < 16; i++ ) { - if ( m_color[i].r < minColor.r ) { minColor.r = m_color[i].r; } - if ( m_color[i].g < minColor.g ) { minColor.g = m_color[i].g; } - if ( m_color[i].b < minColor.b ) { minColor.b = m_color[i].b; } - - if ( m_color[i].r > maxColor.r ) { maxColor.r = m_color[i].r; } - if ( m_color[i].g > maxColor.g ) { maxColor.g = m_color[i].g; } - if ( m_color[i].b > maxColor.b ) { maxColor.b = m_color[i].b; } - } - - // Offset range by 1/16 of the extents - Color32 inset; - inset.r = (maxColor.r - minColor.r) >> 4; - inset.g = (maxColor.g - minColor.g) >> 4; - inset.b = (maxColor.b - minColor.b) >> 4; - - minColor.r = (minColor.r + inset.r <= 255) ? minColor.r + inset.r : 255; - minColor.g = (minColor.g + inset.g <= 255) ? minColor.g + inset.g : 255; - minColor.b = (minColor.b + inset.b <= 255) ? minColor.b + inset.b : 255; - - maxColor.r = (maxColor.r >= inset.r) ? maxColor.r - inset.r : 0; - maxColor.g = (maxColor.g >= inset.g) ? maxColor.g - inset.g : 0; - maxColor.b = (maxColor.b >= inset.b) ? maxColor.b - inset.b : 0; - - *start = minColor; - *end = maxColor; -} - -/// Get color range based on the bounding box. -void ColorBlock::boundsRangeAlpha( Color32 * start, Color32 * end ) const -{ - Color32 minColor( 255, 255, 255, 255 ); - Color32 maxColor( 0, 0, 0, 0 ); - - for ( uint i = 0; i < 16; i++ ) { - if ( m_color[i].r < minColor.r ) { minColor.r = m_color[i].r; } - if ( m_color[i].g < minColor.g ) { minColor.g = m_color[i].g; } - if ( m_color[i].b < minColor.b ) { minColor.b = m_color[i].b; } - if ( m_color[i].a < minColor.a ) { minColor.a = m_color[i].a; } - - if ( m_color[i].r > maxColor.r ) { maxColor.r = m_color[i].r; } - if ( m_color[i].g > maxColor.g ) { maxColor.g = m_color[i].g; } - if ( m_color[i].b > maxColor.b ) { maxColor.b = m_color[i].b; } - if ( m_color[i].a > maxColor.a ) { maxColor.a = m_color[i].a; } - } - - // Offset range by 1/16 of the extents - Color32 inset; - inset.r = (maxColor.r - minColor.r) >> 4; - inset.g = (maxColor.g - minColor.g) >> 4; - inset.b = (maxColor.b - minColor.b) >> 4; - inset.a = (maxColor.a - minColor.a) >> 4; - - minColor.r = (minColor.r + inset.r <= 255) ? minColor.r + inset.r : 255; - minColor.g = (minColor.g + inset.g <= 255) ? minColor.g + inset.g : 255; - minColor.b = (minColor.b + inset.b <= 255) ? minColor.b + inset.b : 255; - minColor.a = (minColor.a + inset.a <= 255) ? minColor.a + inset.a : 255; - - maxColor.r = (maxColor.r >= inset.r) ? maxColor.r - inset.r : 0; - maxColor.g = (maxColor.g >= inset.g) ? maxColor.g - inset.g : 0; - maxColor.b = (maxColor.b >= inset.b) ? maxColor.b - inset.b : 0; - maxColor.a = (maxColor.a >= inset.a) ? maxColor.a - inset.a : 0; - - *start = minColor; - *end = maxColor; -} - -/// Sort colors by abosolute value in their 16 bit representation. -void ColorBlock::sortColorsByAbsoluteValue() -{ - // Dummy selection sort. - for ( uint a = 0; a < 16; a++ ) { - uint max = a; - Color16 cmax( m_color[a] ); - - for ( uint b = a + 1; b < 16; b++ ) { - Color16 cb( m_color[b] ); - - if ( cb.u > cmax.u ) { - max = b; - cmax = cb; - } - } - - dds_swap( m_color[a], m_color[max] ); - } -} diff --git a/src/gl/dds/ColorBlock.h b/src/gl/dds/ColorBlock.h deleted file mode 100644 index e64de4f6f..000000000 --- a/src/gl/dds/ColorBlock.h +++ /dev/null @@ -1,120 +0,0 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2015, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -3. The name of the NIF File Format Library and Tools project may not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -***** END LICENCE BLOCK *****/ - -/* - * This file is based on a similar file from the NVIDIA texture tools - * (http://nvidia-texture-tools.googlecode.com/) - * - * Original license from NVIDIA follows. - */ - -// This code is in the public domain -- castanyo@yahoo.es - -#ifndef _DDS_COLORBLOCK_H -#define _DDS_COLORBLOCK_H - -#include "Color.h" -#include "Image.h" - -/// Uncompressed 4x4 color block. -struct ColorBlock -{ - ColorBlock(); - ColorBlock( const ColorBlock & block ); - ColorBlock( const Image * img, uint x, uint y ); - - void init( const Image * img, uint x, uint y ); - - void swizzleDXT5n(); - void splatX(); - void splatY(); - - bool isSingleColor() const; - uint countUniqueColors() const; - Color32 averageColor() const; - bool hasAlpha() const; - - void diameterRange( Color32 * start, Color32 * end ) const; - void luminanceRange( Color32 * start, Color32 * end ) const; - void boundsRange( Color32 * start, Color32 * end ) const; - void boundsRangeAlpha( Color32 * start, Color32 * end ) const; - - void sortColorsByAbsoluteValue(); - - float volume() const; - - // Accessors - const Color32 * colors() const; - - Color32 color( uint i ) const; - Color32 & color( uint i ); - - Color32 color( uint x, uint y ) const; - Color32 & color( uint x, uint y ); - -private: - - Color32 m_color[4 * 4]; -}; - - -/// Get pointer to block colors. -inline const Color32 * ColorBlock::colors() const -{ - return m_color; -} - -/// Get block color. -inline Color32 ColorBlock::color( uint i ) const -{ - return m_color[i]; -} - -/// Get block color. -inline Color32 & ColorBlock::color( uint i ) -{ - return m_color[i]; -} - -/// Get block color. -inline Color32 ColorBlock::color( uint x, uint y ) const -{ - return m_color[y * 4 + x]; -} - -/// Get block color. -inline Color32 & ColorBlock::color( uint x, uint y ) -{ - return m_color[y * 4 + x]; -} - -#endif // _DDS_COLORBLOCK_H diff --git a/src/gl/dds/Common.h b/src/gl/dds/Common.h deleted file mode 100644 index 9c49a5990..000000000 --- a/src/gl/dds/Common.h +++ /dev/null @@ -1,57 +0,0 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2015, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -3. The name of the NIF File Format Library and Tools project may not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -***** END LICENCE BLOCK *****/ - -#ifndef _DDS_COMMON_H -#define _DDS_COMMON_H - -#include - -#ifndef clamp -#define clamp( x, a, b ) std::min( std::max( (x), (a) ), (b) ) -#endif - -template -inline void -dds_swap( T & a, T & b ) -{ - T tmp = a; - a = b; - b = tmp; -} - -typedef unsigned char uint8; -typedef unsigned short uint16; -typedef unsigned int uint; -typedef unsigned int uint32; -typedef unsigned long long uint64; - -#endif diff --git a/src/gl/dds/DirectDrawSurface.cpp b/src/gl/dds/DirectDrawSurface.cpp deleted file mode 100644 index ffd661517..000000000 --- a/src/gl/dds/DirectDrawSurface.cpp +++ /dev/null @@ -1,1035 +0,0 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2015, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -3. The name of the NIF File Format Library and Tools project may not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -***** END LICENCE BLOCK *****/ - -/* - * This file is based on a similar file from the NVIDIA texture tools - * (http://nvidia-texture-tools.googlecode.com/) - * - * Original license from NVIDIA follows. - */ - -// Copyright NVIDIA Corporation 2007 -- Ignacio Castano -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. - -#include "DirectDrawSurface.h" -#include "BlockDXT.h" -#include "PixelFormat.h" - -#include // printf -#include // sqrt - -/*** declarations ***/ - -#if !defined(MAKEFOURCC) -# define MAKEFOURCC( ch0, ch1, ch2, ch3 ) \ - ( (uint)( (unsigned char)(ch0) ) \ - | ( (uint)( (unsigned char)(ch1) ) << 8 ) \ - | ( (uint)( (unsigned char)(ch2) ) << 16 ) \ - | ( (uint)( (unsigned char)(ch3) ) << 24 ) ) -#endif - -static const uint FOURCC_DDS = MAKEFOURCC( 'D', 'D', 'S', ' ' ); -static const uint FOURCC_DXT1 = MAKEFOURCC( 'D', 'X', 'T', '1' ); -static const uint FOURCC_DXT2 = MAKEFOURCC( 'D', 'X', 'T', '2' ); -static const uint FOURCC_DXT3 = MAKEFOURCC( 'D', 'X', 'T', '3' ); -static const uint FOURCC_DXT4 = MAKEFOURCC( 'D', 'X', 'T', '4' ); -static const uint FOURCC_DXT5 = MAKEFOURCC( 'D', 'X', 'T', '5' ); -static const uint FOURCC_RXGB = MAKEFOURCC( 'R', 'X', 'G', 'B' ); -static const uint FOURCC_ATI1 = MAKEFOURCC( 'A', 'T', 'I', '1' ); -static const uint FOURCC_ATI2 = MAKEFOURCC( 'A', 'T', 'I', '2' ); -static const uint FOURCC_BC5U = MAKEFOURCC( 'B', 'C', '5', 'U' ); - -// 32 bit RGB formats. -static const uint D3DFMT_R8G8B8 = 20; -static const uint D3DFMT_A8R8G8B8 = 21; -static const uint D3DFMT_X8R8G8B8 = 22; -static const uint D3DFMT_R5G6B5 = 23; -static const uint D3DFMT_X1R5G5B5 = 24; -static const uint D3DFMT_A1R5G5B5 = 25; -static const uint D3DFMT_A4R4G4B4 = 26; -static const uint D3DFMT_R3G3B2 = 27; -static const uint D3DFMT_A8 = 28; -static const uint D3DFMT_A8R3G3B2 = 29; -static const uint D3DFMT_X4R4G4B4 = 30; -static const uint D3DFMT_A2B10G10R10 = 31; -static const uint D3DFMT_A8B8G8R8 = 32; -static const uint D3DFMT_X8B8G8R8 = 33; -static const uint D3DFMT_G16R16 = 34; -static const uint D3DFMT_A2R10G10B10 = 35; - -static const uint D3DFMT_A16B16G16R16 = 36; - -// Palette formats. -static const uint D3DFMT_A8P8 = 40; -static const uint D3DFMT_P8 = 41; - -// Luminance formats. -static const uint D3DFMT_L8 = 50; -static const uint D3DFMT_A8L8 = 51; -static const uint D3DFMT_A4L4 = 52; -static const uint D3DFMT_L16 = 81; - -// Floating point formats -static const uint D3DFMT_R16F = 111; -static const uint D3DFMT_G16R16F = 112; -static const uint D3DFMT_A16B16G16R16F = 113; -static const uint D3DFMT_R32F = 114; -static const uint D3DFMT_G32R32F = 115; -static const uint D3DFMT_A32B32G32R32F = 116; - -static const uint DDSD_CAPS = 0x00000001U; -static const uint DDSD_PIXELFORMAT = 0x00001000U; -static const uint DDSD_WIDTH = 0x00000004U; -static const uint DDSD_HEIGHT = 0x00000002U; -static const uint DDSD_PITCH = 0x00000008U; -static const uint DDSD_MIPMAPCOUNT = 0x00020000U; -static const uint DDSD_LINEARSIZE = 0x00080000U; -static const uint DDSD_DEPTH = 0x00800000U; - -static const uint DDSCAPS_COMPLEX = 0x00000008U; -static const uint DDSCAPS_TEXTURE = 0x00001000U; -static const uint DDSCAPS_MIPMAP = 0x00400000U; -static const uint DDSCAPS2_VOLUME = 0x00200000U; -static const uint DDSCAPS2_CUBEMAP = 0x00000200U; - -static const uint DDSCAPS2_CUBEMAP_POSITIVEX = 0x00000400U; -static const uint DDSCAPS2_CUBEMAP_NEGATIVEX = 0x00000800U; -static const uint DDSCAPS2_CUBEMAP_POSITIVEY = 0x00001000U; -static const uint DDSCAPS2_CUBEMAP_NEGATIVEY = 0x00002000U; -static const uint DDSCAPS2_CUBEMAP_POSITIVEZ = 0x00004000U; -static const uint DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x00008000U; -static const uint DDSCAPS2_CUBEMAP_ALL_FACES = 0x0000FC00U; - -static const uint DDPF_ALPHAPIXELS = 0x00000001U; -static const uint DDPF_ALPHA = 0x00000002U; -static const uint DDPF_FOURCC = 0x00000004U; -static const uint DDPF_RGB = 0x00000040U; -static const uint DDPF_PALETTEINDEXED1 = 0x00000800U; -static const uint DDPF_PALETTEINDEXED2 = 0x00001000U; -static const uint DDPF_PALETTEINDEXED4 = 0x00000008U; -static const uint DDPF_PALETTEINDEXED8 = 0x00000020U; -static const uint DDPF_LUMINANCE = 0x00020000U; -static const uint DDPF_ALPHAPREMULT = 0x00008000U; -static const uint DDPF_NORMAL = 0x80000000U; // @@ Custom nv flag. - -// DX10 formats. -enum DXGI_FORMAT -{ - DXGI_FORMAT_UNKNOWN = 0, - - DXGI_FORMAT_R32G32B32A32_TYPELESS = 1, - DXGI_FORMAT_R32G32B32A32_FLOAT = 2, - DXGI_FORMAT_R32G32B32A32_UINT = 3, - DXGI_FORMAT_R32G32B32A32_SINT = 4, - - DXGI_FORMAT_R32G32B32_TYPELESS = 5, - DXGI_FORMAT_R32G32B32_FLOAT = 6, - DXGI_FORMAT_R32G32B32_UINT = 7, - DXGI_FORMAT_R32G32B32_SINT = 8, - - DXGI_FORMAT_R16G16B16A16_TYPELESS = 9, - DXGI_FORMAT_R16G16B16A16_FLOAT = 10, - DXGI_FORMAT_R16G16B16A16_UNORM = 11, - DXGI_FORMAT_R16G16B16A16_UINT = 12, - DXGI_FORMAT_R16G16B16A16_SNORM = 13, - DXGI_FORMAT_R16G16B16A16_SINT = 14, - - DXGI_FORMAT_R32G32_TYPELESS = 15, - DXGI_FORMAT_R32G32_FLOAT = 16, - DXGI_FORMAT_R32G32_UINT = 17, - DXGI_FORMAT_R32G32_SINT = 18, - - DXGI_FORMAT_R32G8X24_TYPELESS = 19, - DXGI_FORMAT_D32_FLOAT_S8X24_UINT = 20, - DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS = 21, - DXGI_FORMAT_X32_TYPELESS_G8X24_UINT = 22, - - DXGI_FORMAT_R10G10B10A2_TYPELESS = 23, - DXGI_FORMAT_R10G10B10A2_UNORM = 24, - DXGI_FORMAT_R10G10B10A2_UINT = 25, - - DXGI_FORMAT_R11G11B10_FLOAT = 26, - - DXGI_FORMAT_R8G8B8A8_TYPELESS = 27, - DXGI_FORMAT_R8G8B8A8_UNORM = 28, - DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 29, - DXGI_FORMAT_R8G8B8A8_UINT = 30, - DXGI_FORMAT_R8G8B8A8_SNORM = 31, - DXGI_FORMAT_R8G8B8A8_SINT = 32, - - DXGI_FORMAT_R16G16_TYPELESS = 33, - DXGI_FORMAT_R16G16_FLOAT = 34, - DXGI_FORMAT_R16G16_UNORM = 35, - DXGI_FORMAT_R16G16_UINT = 36, - DXGI_FORMAT_R16G16_SNORM = 37, - DXGI_FORMAT_R16G16_SINT = 38, - - DXGI_FORMAT_R32_TYPELESS = 39, - DXGI_FORMAT_D32_FLOAT = 40, - DXGI_FORMAT_R32_FLOAT = 41, - DXGI_FORMAT_R32_UINT = 42, - DXGI_FORMAT_R32_SINT = 43, - - DXGI_FORMAT_R24G8_TYPELESS = 44, - DXGI_FORMAT_D24_UNORM_S8_UINT = 45, - DXGI_FORMAT_R24_UNORM_X8_TYPELESS = 46, - DXGI_FORMAT_X24_TYPELESS_G8_UINT = 47, - - DXGI_FORMAT_R8G8_TYPELESS = 48, - DXGI_FORMAT_R8G8_UNORM = 49, - DXGI_FORMAT_R8G8_UINT = 50, - DXGI_FORMAT_R8G8_SNORM = 51, - DXGI_FORMAT_R8G8_SINT = 52, - - DXGI_FORMAT_R16_TYPELESS = 53, - DXGI_FORMAT_R16_FLOAT = 54, - DXGI_FORMAT_D16_UNORM = 55, - DXGI_FORMAT_R16_UNORM = 56, - DXGI_FORMAT_R16_UINT = 57, - DXGI_FORMAT_R16_SNORM = 58, - DXGI_FORMAT_R16_SINT = 59, - - DXGI_FORMAT_R8_TYPELESS = 60, - DXGI_FORMAT_R8_UNORM = 61, - DXGI_FORMAT_R8_UINT = 62, - DXGI_FORMAT_R8_SNORM = 63, - DXGI_FORMAT_R8_SINT = 64, - DXGI_FORMAT_A8_UNORM = 65, - - DXGI_FORMAT_R1_UNORM = 66, - - DXGI_FORMAT_R9G9B9E5_SHAREDEXP = 67, - - DXGI_FORMAT_R8G8_B8G8_UNORM = 68, - DXGI_FORMAT_G8R8_G8B8_UNORM = 69, - - DXGI_FORMAT_BC1_TYPELESS = 70, - DXGI_FORMAT_BC1_UNORM = 71, - DXGI_FORMAT_BC1_UNORM_SRGB = 72, - - DXGI_FORMAT_BC2_TYPELESS = 73, - DXGI_FORMAT_BC2_UNORM = 74, - DXGI_FORMAT_BC2_UNORM_SRGB = 75, - - DXGI_FORMAT_BC3_TYPELESS = 76, - DXGI_FORMAT_BC3_UNORM = 77, - DXGI_FORMAT_BC3_UNORM_SRGB = 78, - - DXGI_FORMAT_BC4_TYPELESS = 79, - DXGI_FORMAT_BC4_UNORM = 80, - DXGI_FORMAT_BC4_SNORM = 81, - - DXGI_FORMAT_BC5_TYPELESS = 82, - DXGI_FORMAT_BC5_UNORM = 83, - DXGI_FORMAT_BC5_SNORM = 84, - - DXGI_FORMAT_B5G6R5_UNORM = 85, - DXGI_FORMAT_B5G5R5A1_UNORM = 86, - DXGI_FORMAT_B8G8R8A8_UNORM = 87, - DXGI_FORMAT_B8G8R8X8_UNORM = 88, -}; - -enum D3D10_RESOURCE_DIMENSION -{ - D3D10_RESOURCE_DIMENSION_UNKNOWN = 0, - D3D10_RESOURCE_DIMENSION_BUFFER = 1, - D3D10_RESOURCE_DIMENSION_TEXTURE1D = 2, - D3D10_RESOURCE_DIMENSION_TEXTURE2D = 3, - D3D10_RESOURCE_DIMENSION_TEXTURE3D = 4, -}; - -/*** implementation ***/ - -void mem_read( Stream & mem, DDSPixelFormat & pf ) -{ - mem_read( mem, pf.size ); - mem_read( mem, pf.flags ); - mem_read( mem, pf.fourcc ); - mem_read( mem, pf.bitcount ); - mem_read( mem, pf.rmask ); - mem_read( mem, pf.gmask ); - mem_read( mem, pf.bmask ); - mem_read( mem, pf.amask ); -} - -void mem_read( Stream & mem, DDSCaps & caps ) -{ - mem_read( mem, caps.caps1 ); - mem_read( mem, caps.caps2 ); - mem_read( mem, caps.caps3 ); - mem_read( mem, caps.caps4 ); -} - -void mem_read( Stream & mem, DDSHeader10 & header ) -{ - mem_read( mem, header.dxgiFormat ); - mem_read( mem, header.resourceDimension ); - mem_read( mem, header.miscFlag ); - mem_read( mem, header.arraySize ); - mem_read( mem, header.reserved ); -} - -void mem_read( Stream & mem, DDSHeader & header ) -{ - mem_read( mem, header.fourcc ); - mem_read( mem, header.size ); - mem_read( mem, header.flags ); - mem_read( mem, header.height ); - mem_read( mem, header.width ); - mem_read( mem, header.pitch ); - mem_read( mem, header.depth ); - mem_read( mem, header.mipmapcount ); - - for ( uint i = 0; i < 11; i++ ) - mem_read( mem, header.reserved[i] ); - - mem_read( mem, header.pf ); - mem_read( mem, header.caps ); - mem_read( mem, header.notused ); - - if ( header.hasDX10Header() ) { - mem_read( mem, header.header10 ); - } -} - - - -DDSHeader::DDSHeader() -{ - this->fourcc = FOURCC_DDS; - this->size = 124; - this->flags = (DDSD_CAPS | DDSD_PIXELFORMAT); - this->height = 0; - this->width = 0; - this->pitch = 0; - this->depth = 0; - this->mipmapcount = 0; - - for ( uint i = 0; i < 11; i++ ) - this->reserved[i] = 0; - - // Store version information on the reserved header attributes. - this->reserved[9] = MAKEFOURCC( 'N', 'V', 'T', 'T' ); - this->reserved[10] = (0 << 16) | (9 << 8) | (5); // major.minor.revision - - this->pf.size = 32; - this->pf.flags = 0; - this->pf.fourcc = 0; - this->pf.bitcount = 0; - this->pf.rmask = 0; - this->pf.gmask = 0; - this->pf.bmask = 0; - this->pf.amask = 0; - this->caps.caps1 = DDSCAPS_TEXTURE; - this->caps.caps2 = 0; - this->caps.caps3 = 0; - this->caps.caps4 = 0; - this->notused = 0; - this->offset = 128; - - this->header10.dxgiFormat = DXGI_FORMAT_UNKNOWN; - this->header10.resourceDimension = D3D10_RESOURCE_DIMENSION_UNKNOWN; - this->header10.miscFlag = 0; - this->header10.arraySize = 0; - this->header10.reserved = 0; -} - -void DDSHeader::setWidth( uint w ) -{ - this->flags |= DDSD_WIDTH; - this->width = w; -} - -void DDSHeader::setHeight( uint h ) -{ - this->flags |= DDSD_HEIGHT; - this->height = h; -} - -void DDSHeader::setDepth( uint d ) -{ - this->flags |= DDSD_DEPTH; - this->height = d; -} - -void DDSHeader::setMipmapCount( uint count ) -{ - if ( count == 0 || count == 1 ) { - this->flags &= ~DDSD_MIPMAPCOUNT; - this->mipmapcount = 0; - - if ( this->caps.caps2 == 0 ) { - this->caps.caps1 = DDSCAPS_TEXTURE; - } else { - this->caps.caps1 = DDSCAPS_TEXTURE | DDSCAPS_COMPLEX; - } - } else { - this->flags |= DDSD_MIPMAPCOUNT; - this->mipmapcount = count; - - this->caps.caps1 |= DDSCAPS_COMPLEX | DDSCAPS_MIPMAP; - } -} - -void DDSHeader::setTexture2D() -{ - this->header10.resourceDimension = D3D10_RESOURCE_DIMENSION_TEXTURE2D; -} - -void DDSHeader::setTexture3D() -{ - this->caps.caps2 = DDSCAPS2_VOLUME; - - this->header10.resourceDimension = D3D10_RESOURCE_DIMENSION_TEXTURE3D; -} - -void DDSHeader::setTextureCube() -{ - this->caps.caps1 |= DDSCAPS_COMPLEX; - this->caps.caps2 = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_ALL_FACES; - - this->header10.resourceDimension = D3D10_RESOURCE_DIMENSION_TEXTURE2D; - this->header10.arraySize = 6; -} - -void DDSHeader::setLinearSize( uint size ) -{ - this->flags &= ~DDSD_PITCH; - this->flags |= DDSD_LINEARSIZE; - this->pitch = size; -} - -void DDSHeader::setPitch( uint pitch ) -{ - this->flags &= ~DDSD_LINEARSIZE; - this->flags |= DDSD_PITCH; - this->pitch = pitch; -} - -void DDSHeader::setFourCC( uint8 c0, uint8 c1, uint8 c2, uint8 c3 ) -{ - // set fourcc pixel format. - this->pf.flags = DDPF_FOURCC; - this->pf.fourcc = MAKEFOURCC( c0, c1, c2, c3 ); - this->pf.bitcount = 0; - this->pf.rmask = 0; - this->pf.gmask = 0; - this->pf.bmask = 0; - this->pf.amask = 0; -} - -void DDSHeader::setPixelFormat( uint bitcount, uint rmask, uint gmask, uint bmask, uint amask ) -{ - // Make sure the masks are correct. - if ( (rmask & gmask) \ - || (rmask & bmask) \ - || (rmask & amask) \ - || (gmask & bmask) \ - || (gmask & amask) \ - || (bmask & amask) ) - { - printf( "DDS: bad RGBA masks, pixel format not set\n" ); - return; - } - - this->pf.flags = DDPF_RGB; - - if ( amask != 0 ) { - this->pf.flags |= DDPF_ALPHAPIXELS; - } - - if ( bitcount == 0 ) { - // Compute bit count from the masks. - uint total = rmask | gmask | bmask | amask; - - while ( total != 0 ) { - bitcount++; - total >>= 1; - } - } - - if ( !(bitcount > 0 && bitcount <= 32) ) { - printf( "DDS: bad bit count, pixel format not set\n" ); - return; - } - - // Align to 8. - if ( bitcount <= 8 ) - bitcount = 8; - else if ( bitcount <= 16 ) - bitcount = 16; - else if ( bitcount <= 24 ) - bitcount = 24; - else - bitcount = 32; - - this->pf.fourcc = 0; //findD3D9Format(bitcount, rmask, gmask, bmask, amask); - this->pf.bitcount = bitcount; - this->pf.rmask = rmask; - this->pf.gmask = gmask; - this->pf.bmask = bmask; - this->pf.amask = amask; -} - -void DDSHeader::setDX10Format( uint format ) -{ - this->pf.flags = 0; - this->header10.dxgiFormat = format; -} - -void DDSHeader::setNormalFlag( bool b ) -{ - if ( b ) - this->pf.flags |= DDPF_NORMAL; - else - this->pf.flags &= ~DDPF_NORMAL; -} - -void DDSHeader::setOffset( uint size ) -{ - this->offset = size; -} - -bool DDSHeader::hasDX10Header() const -{ - return this->pf.flags == 0; -} - -DirectDrawSurface::DirectDrawSurface( const unsigned char * mem, uint size ) : stream( mem, size ), header() -{ - mem_read( stream, header ); - header.offset = 128; -} - -DirectDrawSurface::DirectDrawSurface( const DDSHeader & ddsheader, const unsigned char * mem, uint size ) - : stream( mem, size ), header( ddsheader ) -{ -} - -DirectDrawSurface::~DirectDrawSurface() -{ -} - -bool DirectDrawSurface::isValid() const -{ - if ( header.fourcc != FOURCC_DDS || header.size != 124 ) { - return false; - } - - const uint required = (DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS | DDSD_PIXELFORMAT); - - if ( (header.flags & required) != required ) { - return false; - } - - if ( header.pf.size != 32 ) { - return false; - } - - /* in some files DDSCAPS_TEXTURE is missing: silently ignore */ - /* - if( !(header.caps.caps1 & DDSCAPS_TEXTURE) ) { - return false; - } - */ - - return true; -} - -bool DirectDrawSurface::isSupported() const -{ - if ( header.pf.flags & DDPF_FOURCC ) { - if ( header.pf.fourcc != FOURCC_DXT1 - && header.pf.fourcc != FOURCC_DXT2 - && header.pf.fourcc != FOURCC_DXT3 - && header.pf.fourcc != FOURCC_DXT4 - && header.pf.fourcc != FOURCC_DXT5 - && header.pf.fourcc != FOURCC_RXGB - && header.pf.fourcc != FOURCC_ATI1 - && header.pf.fourcc != FOURCC_ATI2 - && header.pf.fourcc != FOURCC_BC5U ) - { - // Unknown fourcc code. - return false; - } - } else if ( header.pf.flags & DDPF_RGB ) { - // All RGB formats are supported now. - } else { - return false; - } - - if ( isTextureCube() && (header.caps.caps2 & DDSCAPS2_CUBEMAP_ALL_FACES) != DDSCAPS2_CUBEMAP_ALL_FACES ) { - // Cubemaps must contain all faces. - return false; - } - - if ( isTexture3D() ) { - // @@ 3D textures not supported yet. - return false; - } - - return true; -} - - -uint DirectDrawSurface::mipmapCount() const -{ - if ( header.flags & DDSD_MIPMAPCOUNT ) - return header.mipmapcount; - - return 1; -} - - -uint DirectDrawSurface::width() const -{ - if ( header.flags & DDSD_WIDTH ) - return header.width; - - return 1; -} - -uint DirectDrawSurface::height() const -{ - if ( header.flags & DDSD_HEIGHT ) - return header.height; - - return 1; -} - -uint DirectDrawSurface::depth() const -{ - if ( header.flags & DDSD_DEPTH ) - return header.depth; - - return 1; -} - -bool DirectDrawSurface::hasAlpha() const -{ - if ( (header.pf.flags & DDPF_RGB) && (header.pf.amask == 0) ) { - return false; - } else if ( header.pf.fourcc == FOURCC_DXT1 ) { - return false; - } - - return true; -} - -bool DirectDrawSurface::isTexture2D() const -{ - return !isTexture3D() && !isTextureCube(); -} - -bool DirectDrawSurface::isTexture3D() const -{ - return (header.caps.caps2 & DDSCAPS2_VOLUME) != 0; -} - -bool DirectDrawSurface::isTextureCube() const -{ - return (header.caps.caps2 & DDSCAPS2_CUBEMAP) != 0; -} - -void DirectDrawSurface::mipmap( Image * img, uint face, uint mipmap ) -{ - stream.seek( offset( face, mipmap ) ); - - uint w = width(); - uint h = height(); - - // Compute width and height. - for ( uint m = 0; m < mipmap; m++ ) { - w = std::max( 1U, w / 2 ); - h = std::max( 1U, h / 2 ); - } - - img->allocate( w, h ); - - if ( header.pf.flags & DDPF_RGB ) { - readLinearImage( img ); - } else if ( header.pf.flags & DDPF_FOURCC ) { - readBlockImage( img ); - } -} - -void DirectDrawSurface::readLinearImage( Image * img ) -{ - const uint w = img->width(); - const uint h = img->height(); - - uint rshift, rsize; - PixelFormat::maskShiftAndSize( header.pf.rmask, &rshift, &rsize ); - - uint gshift, gsize; - PixelFormat::maskShiftAndSize( header.pf.gmask, &gshift, &gsize ); - - uint bshift, bsize; - PixelFormat::maskShiftAndSize( header.pf.bmask, &bshift, &bsize ); - - uint ashift, asize; - PixelFormat::maskShiftAndSize( header.pf.amask, &ashift, &asize ); - - uint byteCount = (header.pf.bitcount + 7) / 8; - - if ( byteCount > 4 ) { - /* just in case... we could have segfaults later on if byteCount > 4 */ - printf( "DDS: bitcount too large" ); - return; - } - - // set image format: RGB or ARGB - // alpha channel exists if and only if the alpha mask is non-zero - if ( header.pf.amask == 0 ) { - img->setFormat( Image::Format_RGB ); - } else { - img->setFormat( Image::Format_ARGB ); - } - - // Read linear RGB images. - for ( uint y = 0; y < h; y++ ) { - for ( uint x = 0; x < w; x++ ) { - uint c = 0; - mem_read( stream, (unsigned char *)(&c), byteCount ); - - Color32 pixel( 0, 0, 0, 0xFF ); - pixel.r = PixelFormat::convert( c >> rshift, rsize, 8 ); - pixel.g = PixelFormat::convert( c >> gshift, gsize, 8 ); - pixel.b = PixelFormat::convert( c >> bshift, bsize, 8 ); - pixel.a = PixelFormat::convert( c >> ashift, asize, 8 ); - - img->pixel( x, y ) = pixel; - } - } -} - -void DirectDrawSurface::readBlockImage( Image * img ) -{ - const uint w = img->width(); - const uint h = img->height(); - - const uint bw = (w + 3) / 4; - const uint bh = (h + 3) / 4; - - // set image format: RGB or ARGB - // all DXT formats have alpha channel, except DXT1 - if ( header.pf.fourcc == FOURCC_DXT1 ) { - img->setFormat( Image::Format_RGB ); - } else { - img->setFormat( Image::Format_ARGB ); - } - - for ( uint by = 0; by < bh; by++ ) { - for ( uint bx = 0; bx < bw; bx++ ) { - ColorBlock block; - - // Read color block. - readBlock( &block ); - - // Write color block. - for ( uint y = 0; y < std::min( 4U, h - 4 * by ); y++ ) { - for ( uint x = 0; x < std::min( 4U, w - 4 * bx ); x++ ) { - img->pixel( 4 * bx + x, 4 * by + y ) = block.color( x, y ); - } - } - } - } -} - -static Color32 buildNormal( uint8 x, uint8 y ) -{ - float nx = 2 * (x / 255.0f) - 1; - float ny = 2 * (y / 255.0f) - 1; - float nz = 0.0f; - - if ( 1 - nx * nx - ny * ny > 0 ) - nz = sqrtf( 1 - nx * nx - ny * ny ); - - uint8 z = clamp( int(255.0f * (nz + 1) / 2.0f), 0, 255 ); - - return Color32( x, y, z ); -} - - -void DirectDrawSurface::readBlock( ColorBlock * rgba ) -{ - if ( header.pf.fourcc == FOURCC_DXT1 ) { - BlockDXT1 block; - mem_read( stream, block ); - block.decodeBlock( rgba ); - } else if ( header.pf.fourcc == FOURCC_DXT2 - || header.pf.fourcc == FOURCC_DXT3 ) - { - BlockDXT3 block; - mem_read( stream, block ); - block.decodeBlock( rgba ); - } else if ( header.pf.fourcc == FOURCC_DXT4 - || header.pf.fourcc == FOURCC_DXT5 - || header.pf.fourcc == FOURCC_RXGB ) - { - BlockDXT5 block; - mem_read( stream, block ); - block.decodeBlock( rgba ); - - if ( header.pf.fourcc == FOURCC_RXGB ) { - // Swap R & A. - for ( int i = 0; i < 16; i++ ) { - Color32 & c = rgba->color( i ); - uint tmp = c.r; - c.r = c.a; - c.a = tmp; - } - } - } else if ( header.pf.fourcc == FOURCC_ATI1 ) { - BlockATI1 block; - mem_read( stream, block ); - block.decodeBlock( rgba ); - } else if ( header.pf.fourcc == FOURCC_ATI2 || header.pf.fourcc == FOURCC_BC5U ) { - BlockATI2 block; - mem_read( stream, block ); - block.decodeBlock( rgba ); - } - - // If normal flag set, convert to normal. - if ( header.pf.flags & DDPF_NORMAL ) { - if ( header.pf.fourcc == FOURCC_ATI2 || header.pf.fourcc == FOURCC_BC5U ) { - for ( int i = 0; i < 16; i++ ) { - Color32 & c = rgba->color( i ); - c = buildNormal( c.r, c.g ); - } - } else if ( header.pf.fourcc == FOURCC_DXT5 ) { - for ( int i = 0; i < 16; i++ ) { - Color32 & c = rgba->color( i ); - c = buildNormal( c.a, c.g ); - } - } - } -} - - -uint DirectDrawSurface::blockSize() const -{ - switch ( header.pf.fourcc ) { - case FOURCC_DXT1: - case FOURCC_ATI1: - return 8; - case FOURCC_DXT2: - case FOURCC_DXT3: - case FOURCC_DXT4: - case FOURCC_DXT5: - case FOURCC_RXGB: - case FOURCC_ATI2: - case FOURCC_BC5U: - return 16; - } - - // Not a block image. - return 0; -} - -uint DirectDrawSurface::mipmapSize( uint mipmap ) const -{ - uint w = width(); - uint h = height(); - uint d = depth(); - - for ( uint m = 0; m < mipmap; m++ ) { - w = std::max( 1U, w / 2 ); - h = std::max( 1U, h / 2 ); - d = std::max( 1U, d / 2 ); - } - - if ( header.pf.flags & DDPF_FOURCC ) { - // @@ How are 3D textures aligned? - w = (w + 3) / 4; - h = (h + 3) / 4; - return blockSize() * w * h; - } else if ( header.pf.flags & DDPF_RGB ) { - // Align pixels to bytes. - uint byteCount = (header.pf.bitcount + 7) / 8; - - // Align pitch to 4 bytes. - uint pitch = 4 * ( (w * byteCount + 3) / 4 ); - - return pitch * h * d; - } - - printf( "DDS: mipmap format not supported\n" ); - return (0); -} - -uint DirectDrawSurface::faceSize() const -{ - const uint count = mipmapCount(); - uint size = 0; - - for ( uint m = 0; m < count; m++ ) { - size += mipmapSize( m ); - } - - return size; -} - -uint DirectDrawSurface::offset( const uint face, const uint mipmap ) -{ - uint size = header.offset; //sizeof(DDSHeader); - - if ( header.hasDX10Header() ) { - size += 20; // sizeof(DDSHeader10); - } - - if ( face != 0 ) { - size += face * faceSize(); - } - - for ( uint m = 0; m < mipmap; m++ ) { - size += mipmapSize( m ); - } - - return size; -} - - -void DirectDrawSurface::printInfo() const -{ - /* printf("FOURCC: %c%c%c%c\n", ((unsigned char *)&header.fourcc)[0], ((unsigned char *)&header.fourcc)[1], ((unsigned char *)&header.fourcc)[2], ((unsigned char *)&header.fourcc)[3]); */ - printf( "Flags: 0x%.8X\n", header.flags ); - - if ( header.flags & DDSD_CAPS ) printf( "\tDDSD_CAPS\n" ); - if ( header.flags & DDSD_PIXELFORMAT ) printf( "\tDDSD_PIXELFORMAT\n" ); - if ( header.flags & DDSD_WIDTH ) printf( "\tDDSD_WIDTH\n" ); - if ( header.flags & DDSD_HEIGHT ) printf( "\tDDSD_HEIGHT\n" ); - if ( header.flags & DDSD_DEPTH ) printf( "\tDDSD_DEPTH\n" ); - if ( header.flags & DDSD_PITCH ) printf( "\tDDSD_PITCH\n" ); - if ( header.flags & DDSD_LINEARSIZE ) printf( "\tDDSD_LINEARSIZE\n" ); - if ( header.flags & DDSD_MIPMAPCOUNT ) printf( "\tDDSD_MIPMAPCOUNT\n" ); - - printf( "Height: %d\n", header.height ); - printf( "Width: %d\n", header.width ); - printf( "Depth: %d\n", header.depth ); - - if ( header.flags & DDSD_PITCH ) - printf( "Pitch: %d\n", header.pitch ); - else if ( header.flags & DDSD_LINEARSIZE ) - printf( "Linear size: %d\n", header.pitch ); - - printf( "Mipmap count: %d\n", header.mipmapcount ); - - printf( "Pixel Format:\n" ); - /* printf("\tSize: %d\n", header.pf.size); */ - printf( "\tFlags: 0x%.8X\n", header.pf.flags ); - - if ( header.pf.flags & DDPF_RGB ) printf( "\t\tDDPF_RGB\n" ); - if ( header.pf.flags & DDPF_FOURCC ) printf( "\t\tDDPF_FOURCC\n" ); - if ( header.pf.flags & DDPF_ALPHAPIXELS ) printf( "\t\tDDPF_ALPHAPIXELS\n" ); - if ( header.pf.flags & DDPF_ALPHA ) printf( "\t\tDDPF_ALPHA\n" ); - if ( header.pf.flags & DDPF_PALETTEINDEXED1 ) printf( "\t\tDDPF_PALETTEINDEXED1\n" ); - if ( header.pf.flags & DDPF_PALETTEINDEXED2 ) printf( "\t\tDDPF_PALETTEINDEXED2\n" ); - if ( header.pf.flags & DDPF_PALETTEINDEXED4 ) printf( "\t\tDDPF_PALETTEINDEXED4\n" ); - if ( header.pf.flags & DDPF_PALETTEINDEXED8 ) printf( "\t\tDDPF_PALETTEINDEXED8\n" ); - if ( header.pf.flags & DDPF_ALPHAPREMULT ) printf( "\t\tDDPF_ALPHAPREMULT\n" ); - if ( header.pf.flags & DDPF_NORMAL ) printf( "\t\tDDPF_NORMAL\n" ); - - printf( "\tFourCC: '%c%c%c%c'\n", ( (header.pf.fourcc >> 0) & 0xFF ), ( (header.pf.fourcc >> 8) & 0xFF ), ( (header.pf.fourcc >> 16) & 0xFF ), ( (header.pf.fourcc >> 24) & 0xFF ) ); - printf( "\tBit count: %d\n", header.pf.bitcount ); - printf( "\tRed mask: 0x%.8X\n", header.pf.rmask ); - printf( "\tGreen mask: 0x%.8X\n", header.pf.gmask ); - printf( "\tBlue mask: 0x%.8X\n", header.pf.bmask ); - printf( "\tAlpha mask: 0x%.8X\n", header.pf.amask ); - - printf( "Caps:\n" ); - printf( "\tCaps 1: 0x%.8X\n", header.caps.caps1 ); - - if ( header.caps.caps1 & DDSCAPS_COMPLEX ) printf( "\t\tDDSCAPS_COMPLEX\n" ); - if ( header.caps.caps1 & DDSCAPS_TEXTURE ) printf( "\t\tDDSCAPS_TEXTURE\n" ); - if ( header.caps.caps1 & DDSCAPS_MIPMAP ) printf( "\t\tDDSCAPS_MIPMAP\n" ); - - printf( "\tCaps 2: 0x%.8X\n", header.caps.caps2 ); - - if ( header.caps.caps2 & DDSCAPS2_VOLUME ) { - printf( "\t\tDDSCAPS2_VOLUME\n" ); - } else if ( header.caps.caps2 & DDSCAPS2_CUBEMAP ) { - printf( "\t\tDDSCAPS2_CUBEMAP\n" ); - - if ( (header.caps.caps2 & DDSCAPS2_CUBEMAP_ALL_FACES) == DDSCAPS2_CUBEMAP_ALL_FACES ) { - printf( "\t\tDDSCAPS2_CUBEMAP_ALL_FACES\n" ); - } else { - if ( header.caps.caps2 & DDSCAPS2_CUBEMAP_POSITIVEX ) printf( "\t\tDDSCAPS2_CUBEMAP_POSITIVEX\n" ); - if ( header.caps.caps2 & DDSCAPS2_CUBEMAP_NEGATIVEX ) printf( "\t\tDDSCAPS2_CUBEMAP_NEGATIVEX\n" ); - if ( header.caps.caps2 & DDSCAPS2_CUBEMAP_POSITIVEY ) printf( "\t\tDDSCAPS2_CUBEMAP_POSITIVEY\n" ); - if ( header.caps.caps2 & DDSCAPS2_CUBEMAP_NEGATIVEY ) printf( "\t\tDDSCAPS2_CUBEMAP_NEGATIVEY\n" ); - if ( header.caps.caps2 & DDSCAPS2_CUBEMAP_POSITIVEZ ) printf( "\t\tDDSCAPS2_CUBEMAP_POSITIVEZ\n" ); - if ( header.caps.caps2 & DDSCAPS2_CUBEMAP_NEGATIVEZ ) printf( "\t\tDDSCAPS2_CUBEMAP_NEGATIVEZ\n" ); - } - } - - printf( "\tCaps 3: 0x%.8X\n", header.caps.caps3 ); - printf( "\tCaps 4: 0x%.8X\n", header.caps.caps4 ); - - if ( header.pf.flags == 0 ) { - printf( "DX10 Header:\n" ); - printf( "\tDXGI Format: %u\n", header.header10.dxgiFormat ); - printf( "\tResource dimension: %u\n", header.header10.resourceDimension ); - printf( "\tMisc flag: %u\n", header.header10.miscFlag ); - printf( "\tArray size: %u\n", header.header10.arraySize ); - } - - if ( header.reserved[9] == MAKEFOURCC( 'N', 'V', 'T', 'T' ) ) { - int major = (header.reserved[10] >> 16) & 0xFF; - int minor = (header.reserved[10] >> 8) & 0xFF; - int revision = header.reserved[10] & 0xFF; - - printf( "Version:\n" ); - printf( "\tNVIDIA Texture Tools %d.%d.%d\n", major, minor, revision ); - } -} - diff --git a/src/gl/dds/DirectDrawSurface.h b/src/gl/dds/DirectDrawSurface.h deleted file mode 100644 index fcdb58ca1..000000000 --- a/src/gl/dds/DirectDrawSurface.h +++ /dev/null @@ -1,188 +0,0 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2015, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -3. The name of the NIF File Format Library and Tools project may not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -***** END LICENCE BLOCK *****/ - -/* - * This file is based on a similar file from the NVIDIA texture tools - * (http://nvidia-texture-tools.googlecode.com/) - * - * Original license from NVIDIA follows. - */ - -// Copyright NVIDIA Corporation 2007 -- Ignacio Castano -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. - -#ifndef _DDS_DIRECTDRAWSURFACE_H -#define _DDS_DIRECTDRAWSURFACE_H - -#include "Common.h" -#include "Stream.h" -#include "ColorBlock.h" -#include "Image.h" - -struct DDSPixelFormat -{ - uint size; - uint flags; - uint fourcc; - uint bitcount; - uint rmask; - uint gmask; - uint bmask; - uint amask; -}; - -struct DDSCaps -{ - uint caps1; - uint caps2; - uint caps3; - uint caps4; -}; - -/// DDS file header for DX10. -struct DDSHeader10 -{ - uint dxgiFormat; - uint resourceDimension; - uint miscFlag; - uint arraySize; - uint reserved; -}; - -/// DDS file header. -struct DDSHeader -{ - uint fourcc; - uint size; - uint flags; - uint height; - uint width; - uint pitch; - uint depth; - uint mipmapcount; - uint reserved[11]; - DDSPixelFormat pf; - DDSCaps caps; - uint notused; - DDSHeader10 header10; - uint offset; - - - // Helper methods. - DDSHeader(); - - void setWidth( uint w ); - void setHeight( uint h ); - void setDepth( uint d ); - void setMipmapCount( uint count ); - void setTexture2D(); - void setTexture3D(); - void setTextureCube(); - void setLinearSize( uint size ); - void setPitch( uint pitch ); - void setFourCC( uint8 c0, uint8 c1, uint8 c2, uint8 c3 ); - void setPixelFormat( uint bitcount, uint rmask, uint gmask, uint bmask, uint amask ); - void setDX10Format( uint format ); - void setNormalFlag( bool b ); - void setOffset( uint size ); - - bool hasDX10Header() const; -}; - -/// DirectDraw Surface. (DDS) -class DirectDrawSurface -{ -public: - DirectDrawSurface( const unsigned char * mem, uint size ); - DirectDrawSurface( const DDSHeader & header, const unsigned char * mem, uint size ); - ~DirectDrawSurface(); - - bool isValid() const; - bool isSupported() const; - - uint mipmapCount() const; - uint width() const; - uint height() const; - uint depth() const; - bool isTexture2D() const; - bool isTexture3D() const; - bool isTextureCube() const; - bool hasAlpha() const; /* false for DXT1, true for all other DXTs */ - - void mipmap( Image * img, uint f, uint m ); - // void mipmap(FloatImage * img, uint f, uint m); - - void printInfo() const; - -private: - - uint blockSize() const; - uint faceSize() const; - uint mipmapSize( uint m ) const; - - uint offset( uint f, uint m ); - - void readLinearImage( Image * img ); - void readBlockImage( Image * img ); - void readBlock( ColorBlock * rgba ); - -private: - Stream stream; // memory where DDS file resides - DDSHeader header; -}; - -void mem_read( Stream & mem, DDSPixelFormat & pf ); -void mem_read( Stream & mem, DDSCaps & caps ); -void mem_read( Stream & mem, DDSHeader & header ); -void mem_read( Stream & mem, DDSHeader10 & header ); - -#endif // _DDS_DIRECTDRAWSURFACE_H diff --git a/src/gl/dds/Image.cpp b/src/gl/dds/Image.cpp deleted file mode 100644 index fa2bad0d5..000000000 --- a/src/gl/dds/Image.cpp +++ /dev/null @@ -1,143 +0,0 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2015, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -3. The name of the NIF File Format Library and Tools project may not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -***** END LICENCE BLOCK *****/ - -/* - * This file is based on a similar file from the NVIDIA texture tools - * (http://nvidia-texture-tools.googlecode.com/) - * - * Original license from NVIDIA follows. - */ - -// This code is in the public domain -- castanyo@yahoo.es - -#include "Color.h" -#include "Image.h" - -#include // printf - -Image::Image() : m_width( 0 ), m_height( 0 ), m_format( Format_RGB ), m_data( 0 ) -{ -} - -Image::~Image() -{ - free(); -} - -void Image::allocate( uint w, uint h ) -{ - free(); - m_width = w; - m_height = h; - m_data = new Color32[w * h]; -} - -void Image::free() -{ - if ( m_data ) - delete [] m_data; - - m_data = 0; -} - - -uint Image::width() const -{ - return m_width; -} - -uint Image::height() const -{ - return m_height; -} - -const Color32 * Image::scanline( uint h ) const -{ - if ( h >= m_height ) { - printf( "DDS: scanline beyond dimensions of image" ); - return m_data; - } - - return m_data + h * m_width; -} - -Color32 * Image::scanline( uint h ) -{ - if ( h >= m_height ) { - printf( "DDS: scanline beyond dimensions of image" ); - return m_data; - } - - return m_data + h * m_width; -} - -const Color32 * Image::pixels() const -{ - return m_data; -} - -Color32 * Image::pixels() -{ - return m_data; -} - -const Color32 & Image::pixel( uint idx ) const -{ - if ( idx >= m_width * m_height ) { - printf( "DDS: pixel beyond dimensions of image" ); - return m_data[0]; - } - - return m_data[idx]; -} - -Color32 & Image::pixel( uint idx ) -{ - if ( idx >= m_width * m_height ) { - printf( "DDS: pixel beyond dimensions of image" ); - return m_data[0]; - } - - return m_data[idx]; -} - - -Image::Format Image::format() const -{ - return m_format; -} - -void Image::setFormat( Image::Format f ) -{ - m_format = f; -} - diff --git a/src/gl/dds/Image.h b/src/gl/dds/Image.h deleted file mode 100644 index 584511e76..000000000 --- a/src/gl/dds/Image.h +++ /dev/null @@ -1,109 +0,0 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2015, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -3. The name of the NIF File Format Library and Tools project may not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -***** END LICENCE BLOCK *****/ - -/* - * This file is based on a similar file from the NVIDIA texture tools - * (http://nvidia-texture-tools.googlecode.com/) - * - * Original license from NVIDIA follows. - */ - -// This code is in the public domain -- castanyo@yahoo.es - -#ifndef _DDS_IMAGE_H -#define _DDS_IMAGE_H - -#include "Common.h" -#include "Color.h" - -/// 32 bit RGBA image. -class Image -{ -public: - - enum Format - { - Format_RGB, - Format_ARGB, - }; - - Image(); - ~Image(); - - void allocate( uint w, uint h ); - /* - bool load(const char * name); - - void wrap(void * data, uint w, uint h); - void unwrap(); - */ - - uint width() const; - uint height() const; - - const Color32 * scanline( uint h ) const; - Color32 * scanline( uint h ); - - const Color32 * pixels() const; - Color32 * pixels(); - - const Color32 & pixel( uint idx ) const; - Color32 & pixel( uint idx ); - - const Color32 & pixel( uint x, uint y ) const; - Color32 & pixel( uint x, uint y ); - - Format format() const; - void setFormat( Format f ); - -private: - void free(); - -private: - uint m_width; - uint m_height; - Format m_format; - Color32 * m_data; -}; - - -inline const Color32 & Image::pixel( uint x, uint y ) const -{ - return pixel( y * width() + x ); -} - -inline Color32 & Image::pixel( uint x, uint y ) -{ - return pixel( y * width() + x ); -} - -#endif // _DDS_IMAGE_H diff --git a/src/gl/dds/PixelFormat.h b/src/gl/dds/PixelFormat.h deleted file mode 100644 index 248113bb7..000000000 --- a/src/gl/dds/PixelFormat.h +++ /dev/null @@ -1,109 +0,0 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2015, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -3. The name of the NIF File Format Library and Tools project may not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -***** END LICENCE BLOCK *****/ - -/* - * This file is based on a similar file from the NVIDIA texture tools - * (http://nvidia-texture-tools.googlecode.com/) - * - * Original license from NVIDIA follows. - */ - -// Copyright NVIDIA Corporation 2007 -- Ignacio Castano -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. - -#ifndef _DDS_PIXELFORMAT_H -#define _DDS_PIXELFORMAT_H - -#include "Common.h" - -namespace PixelFormat -{ -// Convert component @a c having @a inbits to the returned value having @a outbits. -inline uint convert( uint c, uint inbits, uint outbits ) -{ - if ( inbits == 0 ) { - return 0; - } else if ( inbits >= outbits ) { - // truncate - return c >> (inbits - outbits); - } - - // bitexpand - return ( c << (outbits - inbits) ) | convert( c, inbits, outbits - inbits ); -} - -// Get pixel component shift and size given its mask. -inline void maskShiftAndSize( uint mask, uint * shift, uint * size ) -{ - if ( !mask ) { - *shift = 0; - *size = 0; - return; - } - - *shift = 0; - - while ( (mask & 1) == 0 ) { - ++(*shift); - mask >>= 1; - } - - *size = 0; - - while ( (mask & 1) == 1 ) { - ++(*size); - mask >>= 1; - } -} -} // PixelFormat namespace - -#endif // _DDS_IMAGE_PIXELFORMAT_H diff --git a/src/gl/dds/Stream.cpp b/src/gl/dds/Stream.cpp deleted file mode 100644 index 2385b26d6..000000000 --- a/src/gl/dds/Stream.cpp +++ /dev/null @@ -1,108 +0,0 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2015, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -3. The name of the NIF File Format Library and Tools project may not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -***** END LICENCE BLOCK *****/ - -#include "Stream.h" - -#include // printf -#include // memcpy - -unsigned int Stream::seek( unsigned int p ) -{ - if ( p > size ) { - printf( "DDS: trying to seek beyond end of stream (corrupt file?)" ); - } else { - pos = p; - } - - return pos; -} - -unsigned int mem_read( Stream & mem, unsigned long long & i ) -{ - if ( mem.pos + 8 > mem.size ) { - printf( "DDS: trying to read beyond end of stream (corrupt file?)" ); - return (0); - } - - memcpy( &i, mem.mem + mem.pos, 8 ); // @@ todo: make sure little endian - mem.pos += 8; - return (8); -} - -unsigned int mem_read( Stream & mem, unsigned int & i ) -{ - if ( mem.pos + 4 > mem.size ) { - printf( "DDS: trying to read beyond end of stream (corrupt file?)" ); - return (0); - } - - memcpy( &i, mem.mem + mem.pos, 4 ); // @@ todo: make sure little endian - mem.pos += 4; - return (4); -} - -unsigned int mem_read( Stream & mem, unsigned short & i ) -{ - if ( mem.pos + 2 > mem.size ) { - printf( "DDS: trying to read beyond end of stream (corrupt file?)" ); - return (0); - } - - memcpy( &i, mem.mem + mem.pos, 2 ); // @@ todo: make sure little endian - mem.pos += 2; - return (2); -} - -unsigned int mem_read( Stream & mem, unsigned char & i ) -{ - if ( mem.pos + 1 > mem.size ) { - printf( "DDS: trying to read beyond end of stream (corrupt file?)" ); - return (0); - } - - i = (mem.mem + mem.pos)[0]; - mem.pos += 1; - return (1); -} - -unsigned int mem_read( Stream & mem, unsigned char * i, unsigned int cnt ) -{ - if ( mem.pos + cnt > mem.size ) { - printf( "DDS: trying to read beyond end of stream (corrupt file?)" ); - return (0); - } - - memcpy( i, mem.mem + mem.pos, cnt ); - mem.pos += cnt; - return (cnt); -} - diff --git a/src/gl/dds/Stream.h b/src/gl/dds/Stream.h deleted file mode 100644 index 42ea2bbb1..000000000 --- a/src/gl/dds/Stream.h +++ /dev/null @@ -1,54 +0,0 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2015, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -3. The name of the NIF File Format Library and Tools project may not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -***** END LICENCE BLOCK *****/ - -/* simple memory stream functions with buffer overflow check */ - -#ifndef _STREAM_H -#define _STREAM_H - -struct Stream -{ - const unsigned char * mem; // location in memory - unsigned int size; // size - unsigned int pos; // current position - Stream( const unsigned char * m, unsigned int s ) : mem( m ), size( s ), pos( 0 ) {}; - unsigned int seek( unsigned int p ); -}; - -unsigned int mem_read( Stream & mem, unsigned long long & i ); -unsigned int mem_read( Stream & mem, unsigned int & i ); -unsigned int mem_read( Stream & mem, unsigned short & i ); -unsigned int mem_read( Stream & mem, unsigned char & i ); -unsigned int mem_read( Stream & mem, unsigned char * i, unsigned int cnt ); - -#endif // _STREAM_H - diff --git a/src/gl/dds/dds_api.cpp b/src/gl/dds/dds_api.cpp deleted file mode 100644 index c9cd75d0e..000000000 --- a/src/gl/dds/dds_api.cpp +++ /dev/null @@ -1,123 +0,0 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2015, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -3. The name of the NIF File Format Library and Tools project may not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -***** END LICENCE BLOCK *****/ - -#include "dds_api.h" -#include "Stream.h" -#include "DirectDrawSurface.h" -#include // printf - -int is_a_dds( unsigned char * mem ) // note: use at most first 8 bytes -{ - /* heuristic check to see if mem contains a DDS file */ - /* header.fourcc == FOURCC_DDS */ - if ( (mem[0] != 'D') || (mem[1] != 'D') || (mem[2] != 'S') || (mem[3] != ' ') ) - return (0); - - /* header.size == 124 */ - if ( (mem[4] != 124) || mem[5] || mem[6] || mem[7] ) - return (0); - - return (1); -} - -Image * load_dds( unsigned char * mem, int size, int face, int mipmap ) -{ - DirectDrawSurface dds( mem, size ); /* reads header */ - - /* check if DDS is valid and supported */ - if ( !dds.isValid() ) { - printf( "DDS: not valid; header follows\n" ); - dds.printInfo(); - return (0); - } - - if ( !dds.isSupported() ) { - printf( "DDS: format not supported\n" ); - return (0); - } - - if ( (dds.width() > 65535) || (dds.height() > 65535) ) { - printf( "DDS: dimensions too large\n" ); - return (0); - } - - /* load first face, first mipmap */ - Image * img = new Image(); - dds.mipmap( img, face, mipmap ); - return img; -} - -Image * load_dds( const unsigned char * mem, int size, int face, int mipmap, DDSFormat * format ) -{ - DDSHeader hdr; - hdr.setFourCC( (unsigned char)(format->ddsPixelFormat.dwFourCC >> 0), - (unsigned char)(format->ddsPixelFormat.dwFourCC >> 8), - (unsigned char)(format->ddsPixelFormat.dwFourCC >> 16), - (unsigned char)(format->ddsPixelFormat.dwFourCC >> 24) ); - hdr.setHeight( format->dwHeight ); - hdr.setWidth( format->dwWidth ); - hdr.setTexture2D(); - hdr.setLinearSize( format->dwLinearSize ); - hdr.setMipmapCount( format->dwMipMapCount ); - hdr.setOffset( format->dwSize ); - - //hdr.setPixelFormat(format->ddsPixelFormat.dwBPP, - // format->ddsPixelFormat.dwRMask, - // format->ddsPixelFormat.dwGMask, - // format->ddsPixelFormat.dwBMask, - // format->ddsPixelFormat.dwAMask); - //hdr.setDepth(); - - DirectDrawSurface dds( hdr, mem, size ); /* reads header */ - - /* check if DDS is valid and supported */ - if ( !dds.isValid() ) { - printf( "DDS: not valid; header follows\n" ); - dds.printInfo(); - return (0); - } - - if ( !dds.isSupported() ) { - printf( "DDS: format not supported\n" ); - return (0); - } - - if ( (dds.width() > 65535) || (dds.height() > 65535) ) { - printf( "DDS: dimensions too large\n" ); - return (0); - } - - /* load first face, first mipmap */ - Image * img = new Image(); - dds.mipmap( img, face, mipmap ); - return img; -} diff --git a/src/gl/dds/dds_api.h b/src/gl/dds/dds_api.h deleted file mode 100644 index 40d7e06a6..000000000 --- a/src/gl/dds/dds_api.h +++ /dev/null @@ -1,95 +0,0 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2015, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -3. The name of the NIF File Format Library and Tools project may not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -***** END LICENCE BLOCK *****/ - -#ifndef _DDS_API_H -#define _DDS_API_H - -#include "Image.h" - -#define DDSD_MIPMAPCOUNT 0x00020000 -#define DDPF_FOURCC 0x00000004 - -// DDS format structure -struct DDSFormat -{ - uint32 dwSize; - uint32 dwFlags; - uint32 dwHeight; - uint32 dwWidth; - uint32 dwLinearSize; - uint32 dummy1; - uint32 dwMipMapCount; - uint32 dummy2[11]; - struct - { - uint32 dwSize; - uint32 dwFlags; - uint32 dwFourCC; - uint32 dwBPP; - uint32 dwRMask; - uint32 dwGMask; - uint32 dwBMask; - uint32 dwAMask; - } - ddsPixelFormat; -}; - -// compressed texture pixel formats -#define FOURCC_DXT1 0x31545844 -#define FOURCC_DXT2 0x32545844 -#define FOURCC_DXT3 0x33545844 -#define FOURCC_DXT4 0x34545844 -#define FOURCC_DXT5 0x35545844 - - -//! Check whether the memory array effectively contains a DDS file. -/*! - * Caller must make sure that mem contains at least 8 bytes. - * \return 1 if it is a DDS file, 0 otherwise. - */ -int is_a_dds( unsigned char * mem ); /* use only first 8 bytes of mem */ - - -//! Load a DDS file. -/*! - * \return 0 if load failed, or pointer to Image object otherwise. The caller is responsible for destructing the image object (using delete). - */ -Image * load_dds( unsigned char * mem, int size, int face = 0, int mipmap = 0 ); - - -//! Load a DDS file. -/*! -* \return 0 if load failed, or pointer to Image object otherwise. The caller is responsible for destructing the image object (using delete). -*/ -Image * load_dds( const unsigned char * mem, int size, int face, int mipmap, DDSFormat * format ); - -#endif /* __DDS_API_H */ diff --git a/src/gl/gltex.cpp b/src/gl/gltex.cpp index 6fb6472cd..1bf064b9b 100644 --- a/src/gl/gltex.cpp +++ b/src/gl/gltex.cpp @@ -419,7 +419,7 @@ int TexCache::bind( const QModelIndex & iSource ) glGenTextures( 1, &tx->id ); glBindTexture( GL_TEXTURE_2D, tx->id ); embedTextures.insert( iData, tx ); - texLoad( iData, tx->format, tx->width, tx->height, tx->mipmaps ); + texLoad( iData, tx->format, tx->target, tx->width, tx->height, tx->mipmaps, tx->id ); } catch ( QString & e ) { tx->status = e; @@ -558,7 +558,7 @@ void TexCache::Tex::load() bool TexCache::Tex::saveAsFile( const QModelIndex & index, QString & savepath ) { - texLoad( index, format, width, height, mipmaps ); + texLoad( index, format, target, width, height, mipmaps, id ); if ( savepath.toLower().endsWith( ".tga" ) ) { return texSaveTGA( index, savepath, width, height ); diff --git a/src/gl/gltexloaders.cpp b/src/gl/gltexloaders.cpp index ed4a41e6a..2997be8a4 100644 --- a/src/gl/gltexloaders.cpp +++ b/src/gl/gltexloaders.cpp @@ -30,18 +30,16 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ -#include "gli.hpp" - #include "gltexloaders.h" #include "message.h" #include "model/nifmodel.h" -#include "gl/dds/dds_api.h" -#include "gl/dds/DirectDrawSurface.h" // unused? check if upstream has cleaner or documented API yet +#include "dds.h" #include #include +#include #include #include #include @@ -70,9 +68,21 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ -#define GL_COMPRESSED_LUMINANCE_ALPHA_3DC_ATI 0x8837 -#define FOURCC_ATI2 0x32495441 -#define FOURCC_BC5U 0x55354342 +bool extInitialized = false; +bool extSupported = true; +bool extStorageSupported = true; + +// OpenGL 4.2 +PFNGLTEXSTORAGE2DPROC glTexStorage2D = nullptr; +#ifdef _WIN32 +PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC glCompressedTexSubImage2D = nullptr; +// Fallback +PFNGLCOMPRESSEDTEXIMAGE2DPROC glCompressedTexImage2D = nullptr; +#endif + +#define FOURCC_DXT1 MAKEFOURCC( 'D', 'X', 'T', '1' ) +#define FOURCC_DXT3 MAKEFOURCC( 'D', 'X', 'T', '3' ) +#define FOURCC_DXT5 MAKEFOURCC( 'D', 'X', 'T', '5' ) //! Shift amounts for RGBA conversion static const int rgbashift[4] = { @@ -407,414 +417,6 @@ int texLoadPal( QIODevice & f, int width, int height, int num_mipmaps, int bpp, return m; } -// thanks nvidia for providing the source code to flip dxt images - -typedef struct -{ - unsigned short col0, col1; - unsigned char row[4]; -} DXTColorBlock_t; - -typedef struct -{ - unsigned short row[4]; -} DXT3AlphaBlock_t; - -typedef struct -{ - unsigned char alpha0, alpha1; - unsigned char row[6]; -} DXT5AlphaBlock_t; - -void SwapMem( void * byte1, void * byte2, int size ) -{ - unsigned char * tmp = (unsigned char *)malloc( sizeof(unsigned char) * size ); - if ( !tmp ) - return; - - memcpy( tmp, byte1, size ); - memcpy( byte1, byte2, size ); - memcpy( byte2, tmp, size ); - free( tmp ); -} - -inline void SwapChar( unsigned char * x, unsigned char * y ) -{ - unsigned char z = *x; - *x = *y; - *y = z; -} - -inline void SwapShort( unsigned short * x, unsigned short * y ) -{ - unsigned short z = *x; - *x = *y; - *y = z; -} - -void flipDXT1Blocks( DXTColorBlock_t * Block, int NumBlocks ) -{ - int i; - DXTColorBlock_t * ColorBlock = Block; - - for ( i = 0; i < NumBlocks; i++ ) { - SwapChar( &ColorBlock->row[0], &ColorBlock->row[3] ); - SwapChar( &ColorBlock->row[1], &ColorBlock->row[2] ); - ColorBlock++; - } -} - -void flipDXT3Blocks( DXTColorBlock_t * Block, int NumBlocks ) -{ - int i; - DXTColorBlock_t * ColorBlock = Block; - DXT3AlphaBlock_t * AlphaBlock; - - for ( i = 0; i < NumBlocks; i++ ) { - AlphaBlock = (DXT3AlphaBlock_t *)ColorBlock; - SwapShort( &AlphaBlock->row[0], &AlphaBlock->row[3] ); - SwapShort( &AlphaBlock->row[1], &AlphaBlock->row[2] ); - ColorBlock++; - SwapChar( &ColorBlock->row[0], &ColorBlock->row[3] ); - SwapChar( &ColorBlock->row[1], &ColorBlock->row[2] ); - ColorBlock++; - } -} - -void flipDXT5Alpha( DXT5AlphaBlock_t * Block ) -{ - unsigned long * Bits, Bits0 = 0, Bits1 = 0; - - memcpy( &Bits0, &Block->row[0], sizeof(unsigned char) * 3 ); - memcpy( &Bits1, &Block->row[3], sizeof(unsigned char) * 3 ); - - Bits = ( (unsigned long *)&(Block->row[0]) ); - *Bits &= 0xff000000; - *Bits |= (unsigned char)(Bits1 >> 12) & 0x00000007; - *Bits |= (unsigned char)( (Bits1 >> 15) & 0x00000007 ) << 3; - *Bits |= (unsigned char)( (Bits1 >> 18) & 0x00000007 ) << 6; - *Bits |= (unsigned char)( (Bits1 >> 21) & 0x00000007 ) << 9; - *Bits |= (unsigned char)(Bits1 & 0x00000007) << 12; - *Bits |= (unsigned char)( (Bits1 >> 3) & 0x00000007 ) << 15; - *Bits |= (unsigned char)( (Bits1 >> 6) & 0x00000007 ) << 18; - *Bits |= (unsigned char)( (Bits1 >> 9) & 0x00000007 ) << 21; - - Bits = ( (unsigned long *)&(Block->row[3]) ); - *Bits &= 0xff000000; - *Bits |= (unsigned char)(Bits0 >> 12) & 0x00000007; - *Bits |= (unsigned char)( (Bits0 >> 15) & 0x00000007 ) << 3; - *Bits |= (unsigned char)( (Bits0 >> 18) & 0x00000007 ) << 6; - *Bits |= (unsigned char)( (Bits0 >> 21) & 0x00000007 ) << 9; - *Bits |= (unsigned char)(Bits0 & 0x00000007) << 12; - *Bits |= (unsigned char)( (Bits0 >> 3) & 0x00000007 ) << 15; - *Bits |= (unsigned char)( (Bits0 >> 6) & 0x00000007 ) << 18; - *Bits |= (unsigned char)( (Bits0 >> 9) & 0x00000007 ) << 21; -} - -void flipDXT5Blocks( DXTColorBlock_t * Block, int NumBlocks ) -{ - DXTColorBlock_t * ColorBlock = Block; - DXT5AlphaBlock_t * AlphaBlock; - int i; - - for ( i = 0; i < NumBlocks; i++ ) { - AlphaBlock = (DXT5AlphaBlock_t *)ColorBlock; - - flipDXT5Alpha( AlphaBlock ); - ColorBlock++; - - SwapChar( &ColorBlock->row[0], &ColorBlock->row[3] ); - SwapChar( &ColorBlock->row[1], &ColorBlock->row[2] ); - ColorBlock++; - } -} - -//! Flip DXT blocks vertically (not used in software decompression) -void flipDXT( GLenum glFormat, int width, int height, unsigned char * image ) -{ - int linesize, j; - - DXTColorBlock_t * top; - DXTColorBlock_t * bottom; - int xblocks = width / 4; - int yblocks = height / 4; - - switch ( glFormat ) { - case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: - linesize = xblocks * 8; - - for ( j = 0; j < (yblocks >> 1); j++ ) { - top = (DXTColorBlock_t *)(image + j * linesize); - bottom = (DXTColorBlock_t *)( image + ( ( (yblocks - j) - 1 ) * linesize ) ); - flipDXT1Blocks( top, xblocks ); - flipDXT1Blocks( bottom, xblocks ); - SwapMem( bottom, top, linesize ); - } - break; - case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: - linesize = xblocks * 16; - - for ( j = 0; j < (yblocks >> 1); j++ ) { - top = (DXTColorBlock_t *)(image + j * linesize); - bottom = (DXTColorBlock_t *)( image + ( ( (yblocks - j) - 1 ) * linesize ) ); - flipDXT3Blocks( top, xblocks ); - flipDXT3Blocks( bottom, xblocks ); - SwapMem( bottom, top, linesize ); - } - break; - case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: - linesize = xblocks * 16; - - for ( j = 0; j < (yblocks >> 1); j++ ) { - top = (DXTColorBlock_t *)(image + j * linesize); - bottom = (DXTColorBlock_t *)( image + ( ( (yblocks - j) - 1 ) * linesize ) ); - flipDXT5Blocks( top, xblocks ); - flipDXT5Blocks( bottom, xblocks ); - SwapMem( bottom, top, linesize ); - } - break; - default: - return; - } -} - -/*! Load a DXT compressed DDS texture from file - * - * @param f File to load from - * @param null Format - * @param null Block size - * @param null Width - * @param null Height - * @param mipmaps The number of mipmaps to read - * @param null Flip - * @return The total number of mipmaps - */ -GLuint texLoadDXT( QIODevice & f, GLenum glFormat, int /*blockSize*/, quint32 /*width*/, quint32 /*height*/, quint32 mipmaps, bool /*flipV*/ = false ) -{ -/* -#ifdef WIN32 - if ( !_glCompressedTexImage2D ) - { -#endif -*/ - // load the pixels - f.seek( 0 ); - QByteArray bytes = f.readAll(); - GLuint m = 0; - - while ( m < mipmaps ) { - // load face 0, mipmap m - Image * img = load_dds( (unsigned char *)bytes.data(), bytes.size(), 0, m ); - - if ( !img ) - return (0); - - // convert texture to OpenGL RGBA format - unsigned int w = img->width(); - unsigned int h = img->height(); - GLubyte * pixels = new GLubyte[w * h * 4]; - Color32 * src = img->pixels(); - GLubyte * dst = pixels; - - //qDebug() << "flipV = " << flipV; - if ( glFormat == GL_COMPRESSED_LUMINANCE_ALPHA_3DC_ATI ) { - for ( quint32 y = 0; y < h; y++ ) { - for ( quint32 x = 0; x < w; x++ ) { - *dst++ = src->r; - *dst++ = src->g; - *dst++ = 255 - src->b; - *dst++ = 255; - src++; - } - } - } else { - for ( quint32 y = 0; y < h; y++ ) { - for ( quint32 x = 0; x < w; x++ ) { - *dst++ = src->r; - *dst++ = src->g; - *dst++ = src->b; - *dst++ = src->a; - src++; - } - } - } - - delete img; - - // load the texture into OpenGL - glTexImage2D( GL_TEXTURE_2D, m++, 4, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels ); - delete [] pixels; - } - - m = generateMipMaps( m ); - return m; -/* -#ifdef WIN32 - } - GLubyte * pixels = (GLubyte *) malloc( ( ( width + 3 ) / 4 ) * ( ( height + 3 ) / 4 ) * blockSize ); - unsigned int w = width, h = height, s; - unsigned int m = 0; - - while ( m < mipmaps ) - { - w = width >> m; - h = height >> m; - - if ( w == 0 ) w = 1; - if ( h == 0 ) h = 1; - - s = ((w+3)/4) * ((h+3)/4) * blockSize; - - if ( f.read( (char *) pixels, s ) != s ) - { - free( pixels ); - throw QString ( "unexpected EOF" ); - } - - if ( flipV ) - flipDXT( glFormat, w, h, pixels ); - - _glCompressedTexImage2D( GL_TEXTURE_2D, m++, glFormat, w, h, 0, s, pixels ); - - if ( w == 1 && h == 1 ) - break; - } - - if ( w > 1 || h > 1 ) - return 1; - else - return m; -#endif -*/ -} - -//! Load a (possibly compressed) dds texture. -GLuint texLoadDDS( QIODevice & f, QString & texformat ) -{ - char tag[4]; - f.read( &tag[0], 4 ); - DDSFormat ddsHeader; - - if ( strncmp( tag, "DDS ", 4 ) != 0 || f.read( (char *)&ddsHeader, sizeof(DDSFormat) ) != sizeof( DDSFormat ) ) - throw QString( "not a DDS file" ); - - texformat = "DDS"; - - if ( !( ddsHeader.dwFlags & DDSD_MIPMAPCOUNT ) ) - ddsHeader.dwMipMapCount = 1; - - if ( !( isPowerOfTwo( ddsHeader.dwWidth ) && isPowerOfTwo( ddsHeader.dwHeight ) ) ) - throw QString( "image dimensions must be power of two" ); - - f.seek( ddsHeader.dwSize + 4 ); - - if ( ddsHeader.ddsPixelFormat.dwFlags & DDPF_FOURCC ) { - int blockSize = 8; - GLenum glFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; - - switch ( ddsHeader.ddsPixelFormat.dwFourCC ) { - case FOURCC_DXT1: - glFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; - blockSize = 8; - texformat += " (DXT1)"; - break; - case FOURCC_DXT3: - glFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; - blockSize = 16; - texformat += " (DXT3)"; - break; - case FOURCC_DXT5: - glFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; - blockSize = 16; - texformat += " (DXT5)"; - break; - case FOURCC_ATI2: - case FOURCC_BC5U: - glFormat = GL_COMPRESSED_LUMINANCE_ALPHA_3DC_ATI; - blockSize = 16; - texformat += " (ATI2)"; - break; - default: - throw QString( "unknown texture compression" ); - } - - return texLoadDXT( f, glFormat, blockSize, ddsHeader.dwWidth, ddsHeader.dwHeight, ddsHeader.dwMipMapCount ); - } else if ( ddsHeader.ddsPixelFormat.dwFlags & 0x20 ) { - // DDPF_PALETTEINDEXED8 - texformat += " (PAL)"; - - // Palette format: 256 RGBA quads (1024 bytes), starting after header - - quint32 colormap[256]; - - if ( f.read( (char *)colormap, 4 * 256 ) != 4 * 256 ) - throw QString( "unexpected EOF" ); - - return texLoadPal( f, ddsHeader.dwWidth, ddsHeader.dwHeight, ddsHeader.dwMipMapCount, - ddsHeader.ddsPixelFormat.dwBPP, ddsHeader.ddsPixelFormat.dwBPP / 8, - (const quint32 *)colormap, false, false, false ); - } else { - texformat += " (RAW)"; - - if ( ddsHeader.ddsPixelFormat.dwRMask != 0 && ddsHeader.ddsPixelFormat.dwGMask == 0 && ddsHeader.ddsPixelFormat.dwBMask == 0 ) { - // fixup greyscale - ddsHeader.ddsPixelFormat.dwGMask = ddsHeader.ddsPixelFormat.dwRMask; - ddsHeader.ddsPixelFormat.dwBMask = ddsHeader.ddsPixelFormat.dwRMask; - } - - return texLoadRaw( f, ddsHeader.dwWidth, ddsHeader.dwHeight, - ddsHeader.dwMipMapCount, ddsHeader.ddsPixelFormat.dwBPP, ddsHeader.ddsPixelFormat.dwBPP / 8, - &ddsHeader.ddsPixelFormat.dwRMask ); - } -} - -/*! Load a DXT compressed texture - * - * @param hdr Description of the texture - * @param pixels The pixel data - * @param size The size of the texture - * @return The total number of mipmaps - */ -GLuint texLoadDXT( DDSFormat & hdr, const quint8 * pixels, uint size ) -{ - int m = 0; - - while ( m < (int)hdr.dwMipMapCount ) { - // load face 0, mipmap m - Image * img = load_dds( pixels, (int)size, 0, m, &hdr ); - - if ( !img ) - return (0); - - // convert texture to OpenGL RGBA format - unsigned int w = img->width(); - unsigned int h = img->height(); - GLubyte * pixels = new GLubyte[w * h * 4]; - Color32 * src = img->pixels(); - GLubyte * dst = pixels; - - //qDebug() << "flipV = " << flipV; - for ( quint32 y = 0; y < h; y++ ) { - for ( quint32 x = 0; x < w; x++ ) { - *dst++ = src->r; - *dst++ = src->g; - *dst++ = src->b; - *dst++ = src->a; - src++; - } - } - - delete img; - // load the texture into OpenGL - glTexImage2D( GL_TEXTURE_2D, m++, 4, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels ); - delete [] pixels; - } - - m = generateMipMaps( m ); - return m; -} - - // TGA constants // Note that TGA_X_RLE = TGA_X + 8 @@ -828,7 +430,7 @@ GLuint texLoadDXT( DDSFormat & hdr, const quint8 * pixels, uint size ) #define TGA_GREY_RLE 11 //! Load a TGA texture. -GLuint texLoadTGA( QIODevice & f, QString & texformat ) +GLuint texLoadTGA( QIODevice & f, QString & texformat, GLuint & width, GLuint & height ) { // see http://en.wikipedia.org/wiki/Truevision_TGA for a lot of this texformat = "TGA"; @@ -848,8 +450,8 @@ GLuint texLoadTGA( QIODevice & f, QString & texformat ) //quint8 alphaDepth = hdr[17] & 15; bool flipV = !( hdr[17] & 32 ); bool flipH = hdr[17] & 16; - quint16 width = hdr[12] + 256 * hdr[13]; - quint16 height = hdr[14] + 256 * hdr[15]; + width = hdr[12] + 256 * hdr[13]; + height = hdr[14] + 256 * hdr[15]; if ( !( isPowerOfTwo( width ) && isPowerOfTwo( height ) ) ) throw QString( "image dimensions must be power of two" ); @@ -966,7 +568,7 @@ quint16 get16( quint8 * x ) } //! Load a BMP texture. -GLuint texLoadBMP( QIODevice & f, QString & texformat ) +GLuint texLoadBMP( QIODevice & f, QString & texformat, GLuint & width, GLuint & height ) { // read in bmp header quint8 hdr[54]; @@ -977,8 +579,8 @@ GLuint texLoadBMP( QIODevice & f, QString & texformat ) texformat = "BMP"; - unsigned int width = get32( &hdr[18] ); - unsigned int height = get32( &hdr[22] ); + width = get32( &hdr[18] ); + height = get32( &hdr[22] ); unsigned int bpp = get16( &hdr[28] ); unsigned int compression = get32( &hdr[30] ); unsigned int offset = get32( &hdr[10] ); @@ -996,16 +598,6 @@ GLuint texLoadBMP( QIODevice & f, QString & texformat ) } break; - // Since when can a BMP contain DXT compressed textures? - case FOURCC_DXT5: - texformat += " (DXT5)"; - return texLoadDXT( f, compression, 16, width, height, 1, true ); - case FOURCC_DXT3: - texformat += " (DXT3)"; - return texLoadDXT( f, compression, 16, width, height, 1, true ); - case FOURCC_DXT1: - texformat += " (DXT1)"; - return texLoadDXT( f, compression, 8, width, height, 1, true ); } throw QString( "unknown image sub format" ); @@ -1019,8 +611,56 @@ GLuint texLoadBMP( QIODevice & f, QString & texformat ) return 0; } +GLuint texLoadDDS( const QString & filepath, QString & format, GLenum & target, GLuint & width, GLuint & height, GLuint & mipmaps, QByteArray & data, GLuint & id ) +{ + if ( !extInitialized ) { + glTexStorage2D = (PFNGLTEXSTORAGE2DPROC)SOIL_GL_GetProcAddress( "glTexStorage2D" ); +#ifdef _WIN32 + glCompressedTexSubImage2D = (PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC)SOIL_GL_GetProcAddress( "glCompressedTexSubImage2D" ); +#endif + if ( !glTexStorage2D || !glCompressedTexSubImage2D ) + extStorageSupported = false; + + extInitialized = true; + } + + GLuint result = 0; + gli::texture texture; + if ( extStorageSupported ) { + texture = load_if_valid( data.constData(), data.size() ); + if ( !texture.empty() ) + result = GLI_create_texture( texture, target, id ); + } else { +#ifdef _WIN32 + glCompressedTexImage2D = (PFNGLCOMPRESSEDTEXIMAGE2DPROC)SOIL_GL_GetProcAddress( "glCompressedTexImage2D" ); +#endif + if ( glCompressedTexImage2D ) { + texture = load_if_valid( data.constData(), data.size() ); + if ( !texture.empty() ) + result = GLI_create_texture_fallback( texture, target, id ); + } + } + + if ( result ) { + id = result; + mipmaps = (GLuint)texture.levels(); + } else { + mipmaps = 0; + QString file = filepath; + file.replace( '/', "\\" ); + Message::append( "One or more textures failed to load.", + QString( "'%1' is corrupt or unsupported." ).arg( file ) + ); + } + + if ( !texture.empty() ) + texture.clear(); + + return mipmaps; +} + // (public function, documented in gltexloaders.h) -bool texLoad( const QModelIndex & iData, QString & texformat, GLuint & width, GLuint & height, GLuint & mipmaps ) +bool texLoad( const QModelIndex & iData, QString & texformat, GLenum & target, GLuint & width, GLuint & height, GLuint & mipmaps, GLuint & id ) { bool ok = false; const NifModel * nif = qobject_cast( iData.model() ); @@ -1094,21 +734,19 @@ bool texLoad( const QModelIndex & iData, QString & texformat, GLuint & width, GL } } - DDSFormat hdr; - hdr.dwSize = 0; - hdr.dwFlags = DDPF_FOURCC; + DDS_HEADER hdr = {}; + hdr.dwSize = sizeof( hdr ); + hdr.dwHeaderFlags = DDS_HEADER_FLAGS_TEXTURE | DDS_HEADER_FLAGS_LINEARSIZE | DDS_HEADER_FLAGS_MIPMAP; hdr.dwHeight = height; - hdr.dwWidth = width; - hdr.dwLinearSize = 0; + hdr.dwWidth = width; hdr.dwMipMapCount = mipmaps; - hdr.ddsPixelFormat.dwSize = 0; - hdr.ddsPixelFormat.dwFlags = DDPF_FOURCC; - hdr.ddsPixelFormat.dwFourCC = FOURCC_DXT1; - hdr.ddsPixelFormat.dwBPP = bpp; - hdr.ddsPixelFormat.dwRMask = mask[0]; - hdr.ddsPixelFormat.dwGMask = mask[1]; - hdr.ddsPixelFormat.dwBMask = mask[2]; - hdr.ddsPixelFormat.dwAMask = mask[3]; + hdr.ddspf.dwFlags = DDS_FOURCC; + hdr.ddspf.dwSize = sizeof( DDS_PIXELFORMAT ); + hdr.ddspf.dwRBitMask = mask[0]; + hdr.ddspf.dwGBitMask = mask[1]; + hdr.ddspf.dwBBitMask = mask[2]; + hdr.ddspf.dwRBitMask = mask[3]; + hdr.dwSurfaceFlags = DDS_SURFACE_FLAGS_TEXTURE | DDS_SURFACE_FLAGS_MIPMAP; texformat = "NIF"; @@ -1150,24 +788,33 @@ bool texLoad( const QModelIndex & iData, QString & texformat, GLuint & width, GL break; case 4: //PX_FMT_DXT1 texformat += " (DXT1)"; - hdr.ddsPixelFormat.dwFourCC = FOURCC_DXT1; - ok = ( 0 != texLoadDXT( hdr, (const unsigned char *)buf.data().data(), buf.size() ) ); + hdr.ddspf.dwFourCC = FOURCC_DXT1; + hdr.dwPitchOrLinearSize = width * height / 2; break; case 5: //PX_FMT_DXT3 texformat += " (DXT3)"; - hdr.ddsPixelFormat.dwFourCC = FOURCC_DXT3; - ok = ( 0 != texLoadDXT( hdr, (const unsigned char *)buf.data().data(), buf.size() ) ); + hdr.ddspf.dwFourCC = FOURCC_DXT3; + hdr.dwPitchOrLinearSize = width * height; break; case 6: //PX_FMT_DXT5 texformat += " (DXT5)"; - hdr.ddsPixelFormat.dwFourCC = FOURCC_DXT5; - ok = ( 0 != texLoadDXT( hdr, (const unsigned char *)buf.data().data(), buf.size() ) ); + hdr.ddspf.dwFourCC = FOURCC_DXT5; + hdr.dwPitchOrLinearSize = width * height; break; } - if ( ok ) { - glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, (GLint *)&width ); - glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, (GLint *)&height ); + if ( format >= 4 && format <= 6 ) { + // Create and prepend DDS header + char dds[sizeof(hdr)]; + memcpy( dds, &hdr, sizeof(hdr) ); + + buf.buffer().prepend( QByteArray::fromRawData( dds, sizeof( hdr ) ) ); + buf.buffer().prepend( QByteArray::fromStdString( "DDS " ) ); + + mipmaps = texLoadDDS( QString( "[%1] NiPixelData" ).arg( nif->getBlockNumber( iData ) ), + texformat, target, width, height, mipmaps, buf.buffer(), id ); + + ok = (mipmaps > 0); } } @@ -1175,7 +822,7 @@ bool texLoad( const QModelIndex & iData, QString & texformat, GLuint & width, GL } //! Load NiPixelData or NiPersistentSrcTextureRendererData from a NifModel -GLuint texLoadNIF( QIODevice & f, QString & texformat ) +GLuint texLoadNIF( QIODevice & f, QString & texformat, GLuint & width, GLuint & height, GLuint & id ) { GLuint mipmaps = 0; @@ -1192,24 +839,13 @@ GLuint texLoadNIF( QIODevice & f, QString & texformat ) if ( !iData.isValid() || iData == QModelIndex() ) throw QString( "this is not a normal .nif file; there should be only pixel data as root blocks" ); - GLuint width, height; - texLoad( iData, texformat, width, height, mipmaps ); + GLenum target = 0; + texLoad( iData, texformat, target, width, height, mipmaps, id ); } return mipmaps; } -bool extInitialized = false; -bool extSupported = true; -bool extStorageSupported = true; - -// OpenGL 4.2 -PFNGLTEXSTORAGE2DPROC glTexStorage2D = nullptr; -#ifdef _WIN32 -PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC glCompressedTexSubImage2D = nullptr; -// Fallback -PFNGLCOMPRESSEDTEXIMAGE2DPROC glCompressedTexImage2D = nullptr; -#endif //! Create texture with glTexStorage2D using GLI GLuint GLI_create_texture( gli::texture& texture, GLenum& target, GLuint& id ) @@ -1341,24 +977,25 @@ GLuint GLI_create_texture_fallback( gli::texture& texture, GLenum & target, GLui gli::texture load_if_valid( const char * data, int size ) { using namespace gli; + using namespace gli::detail; - if ( strncmp( data, gli::detail::FOURCC_DDS, 4 ) != 0 || size < sizeof( gli::detail::dds_header ) ) + if ( strncmp( data, FOURCC_DDS, 4 ) != 0 || size < sizeof( dds_header ) ) return texture(); - std::size_t Offset = sizeof( gli::detail::FOURCC_DDS ); + std::size_t Offset = sizeof( FOURCC_DDS ); - gli::detail::dds_header const & Header( *reinterpret_cast(data + Offset) ); - Offset += sizeof( gli::detail::dds_header ); + dds_header const & Header( *reinterpret_cast(data + Offset) ); + Offset += sizeof( dds_header ); - gli::detail::dds_header10 Header10; - if ( (Header.Format.flags & DDPF_FOURCC) && (Header.Format.fourCC == dx::D3DFMT_DX10 || Header.Format.fourCC == dx::D3DFMT_GLI1) ) { + dds_header10 Header10; + if ( (Header.Format.flags & dx::DDPF_FOURCC) && (Header.Format.fourCC == dx::D3DFMT_DX10 || Header.Format.fourCC == dx::D3DFMT_GLI1) ) { std::memcpy( &Header10, data + Offset, sizeof( Header10 ) ); - Offset += sizeof( gli::detail::dds_header10 ); + Offset += sizeof( dds_header10 ); } dx DX; - gli::format Format( static_cast(gli::FORMAT_INVALID) ); + format Format( static_cast(FORMAT_INVALID) ); if ( (Header.Format.flags & (dx::DDPF_RGB | dx::DDPF_ALPHAPIXELS | dx::DDPF_ALPHA | dx::DDPF_YUV | dx::DDPF_LUMINANCE)) && Format == static_cast(gli::FORMAT_INVALID) && Header.Format.bpp != 0 ) { switch ( Header.Format.bpp ) { default: @@ -1430,22 +1067,22 @@ gli::texture load_if_valid( const char * data, int size ) break; } } - } else if ( (Header.Format.flags & DDPF_FOURCC) && (Header.Format.fourCC != dx::D3DFMT_DX10) && (Header.Format.fourCC != dx::D3DFMT_GLI1) && (Format == static_cast(gli::FORMAT_INVALID)) ) { - dx::d3dfmt const FourCC = gli::detail::remap_four_cc( Header.Format.fourCC ); + } else if ( (Header.Format.flags & dx::DDPF_FOURCC) && (Header.Format.fourCC != dx::D3DFMT_DX10) && (Header.Format.fourCC != dx::D3DFMT_GLI1) && (Format == static_cast(gli::FORMAT_INVALID)) ) { + dx::d3dfmt const FourCC = remap_four_cc( Header.Format.fourCC ); Format = DX.find( FourCC ); } else if ( Header.Format.fourCC == dx::D3DFMT_DX10 || Header.Format.fourCC == dx::D3DFMT_GLI1 ) Format = DX.find( Header.Format.fourCC, Header10.Format ); - if ( Format == static_cast(gli::FORMAT_INVALID) ) + if ( Format == static_cast(FORMAT_INVALID) ) return texture(); size_t const MipMapCount = (Header.Flags & DDSD_MIPMAPCOUNT) ? Header.MipMapLevels : 1; size_t FaceCount = 1; - if ( Header.CubemapFlags & gli::detail::DDSCAPS2_CUBEMAP ) - FaceCount = int( glm::bitCount( Header.CubemapFlags & gli::detail::DDSCAPS2_CUBEMAP_ALLFACES ) ); + if ( Header.CubemapFlags & DDSCAPS2_CUBEMAP ) + FaceCount = int( glm::bitCount( Header.CubemapFlags & DDSCAPS2_CUBEMAP_ALLFACES ) ); size_t DepthCount = 1; - if ( Header.CubemapFlags & gli::detail::DDSCAPS2_VOLUME ) + if ( Header.CubemapFlags & DDSCAPS2_VOLUME ) DepthCount = Header.Depth; texture Texture( @@ -1491,67 +1128,26 @@ bool texLoad( const QString & filepath, QString & format, GLenum & target, GLuin throw QString( "could not open buffer" ); bool isSupported = true; - if ( filepath.endsWith( ".dds", Qt::CaseInsensitive ) ) { - - if ( !extInitialized ) { - glTexStorage2D = (PFNGLTEXSTORAGE2DPROC)SOIL_GL_GetProcAddress( "glTexStorage2D" ); -#ifdef _WIN32 - glCompressedTexSubImage2D = (PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC)SOIL_GL_GetProcAddress( "glCompressedTexSubImage2D" ); -#endif - if ( !glTexStorage2D || !glCompressedTexSubImage2D ) - extStorageSupported = false; - - extInitialized = true; - } - - GLuint result = 0; - gli::texture texture; - if ( extStorageSupported ) { - texture = load_if_valid( data.constData(), data.size() ); - if ( !texture.empty() ) - result = GLI_create_texture( texture, target, id ); - } else { -#ifdef _WIN32 - glCompressedTexImage2D = (PFNGLCOMPRESSEDTEXIMAGE2DPROC)SOIL_GL_GetProcAddress( "glCompressedTexImage2D" ); -#endif - if ( !glCompressedTexImage2D ) { - // Legacy DDS loader - //mipmaps = texLoadDDS( f, format ); - } else { - texture = load_if_valid( data.constData(), data.size() ); - if ( !texture.empty() ) - result = GLI_create_texture_fallback( texture, target, id ); - } - } - - if ( result ) { - id = result; - mipmaps = (GLuint)texture.levels(); - } else { - isSupported = false; - QString file = filepath; - file.replace( '/', "\\" ); - Message::append( "One or more textures failed to load.", - QString( "'%1' is corrupt or unsupported." ).arg( file ) - ); - } - - if ( !texture.empty() ) - texture.clear(); - - } + if ( filepath.endsWith( ".dds", Qt::CaseInsensitive ) ) + mipmaps = texLoadDDS( filepath, format, target, width, height, mipmaps, data, id ); else if ( filepath.endsWith( ".tga", Qt::CaseInsensitive ) ) - mipmaps = texLoadTGA( f, format ); + mipmaps = texLoadTGA( f, format, width, height ); else if ( filepath.endsWith( ".bmp", Qt::CaseInsensitive ) ) - mipmaps = texLoadBMP( f, format ); + mipmaps = texLoadBMP( f, format, width, height ); else if ( filepath.endsWith( ".nif", Qt::CaseInsensitive ) || filepath.endsWith( ".texcache", Qt::CaseInsensitive ) ) - mipmaps = texLoadNIF( f, format ); + mipmaps = texLoadNIF( f, format, width, height, id ); else isSupported = false; f.close(); data.clear(); + if ( mipmaps == 0 ) + isSupported = false; + + if ( !target ) + target = GL_TEXTURE_2D; + if ( isSupported ) { GLenum t = target; if ( target == GL_TEXTURE_CUBE_MAP ) @@ -1563,7 +1159,7 @@ bool texLoad( const QString & filepath, QString & format, GLenum & target, GLuin throw QString( "unknown texture format" ); } - return mipmaps > 0; + return isSupported; } bool texIsSupported( const QString & filepath ) @@ -1731,10 +1327,11 @@ bool texSaveDDS( const QModelIndex & index, const QString & filepath, const GLui break; case 5: fourcc = FOURCC_DXT3; + break; case 6: fourcc = FOURCC_DXT5; break; - default: // again, how did we get here? + default: fourcc = 0; break; } @@ -2074,7 +1671,7 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) //qDebug() << "Copying from GL buffer"; GLuint width, height, mipmaps, id; - GLuint target = 0x0DE1; + GLuint target = GL_TEXTURE_2D; QString format; // fastest way to get parameters and ensure texture is active @@ -2162,37 +1759,38 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) //return false; } else if ( filepath.endsWith( ".dds", Qt::CaseInsensitive ) ) { //qDebug() << "Will copy from DDS data"; - DDSFormat ddsHeader; + DDS_HEADER ddsHeader; char tag[4]; f.read( &tag[0], 4 ); - if ( strncmp( tag, "DDS ", 4 ) != 0 || f.read( (char *)&ddsHeader, sizeof(DDSFormat) ) != sizeof( DDSFormat ) ) + if ( strncmp( tag, "DDS ", 4 ) != 0 || f.read( (char *)&ddsHeader, sizeof(DDS_HEADER) ) != sizeof(DDS_HEADER) ) throw QString( "not a DDS file" ); - qDebug() << "Size: " << ddsHeader.dwSize << "Flags" << ddsHeader.dwFlags << "Height" << ddsHeader.dwHeight << "Width" << ddsHeader.dwWidth; - qDebug() << "FourCC:" << ddsHeader.ddsPixelFormat.dwFourCC; + qDebug() << "Size: " << ddsHeader.dwSize << "Flags" << ddsHeader.dwHeaderFlags << "Height" << ddsHeader.dwHeight << "Width" << ddsHeader.dwWidth; + qDebug() << "FourCC:" << ddsHeader.ddspf.dwFourCC; - if ( ddsHeader.ddsPixelFormat.dwFlags & DDPF_FOURCC ) { - switch ( ddsHeader.ddsPixelFormat.dwFourCC ) { + if ( ddsHeader.ddspf.dwFlags & DDS_FOURCC ) { + switch ( ddsHeader.ddspf.dwFourCC ) { case FOURCC_DXT1: //qDebug() << "DXT1"; nif->set( iData, "Pixel Format", 4 ); break; + case FOURCC_DXT3: + //qDebug() << "DXT3"; + nif->set( iData, "Pixel Format", 5 ); + break; case FOURCC_DXT5: //qDebug() << "DXT5"; - nif->set( iData, "Pixel Format", 5 ); + nif->set( iData, "Pixel Format", 6 ); break; default: - // don't know how eg. DXT3 can be stored in NIF - qCCritical( nsIo ) << QObject::tr( "Unsupported DDS format: %1 %2" ).arg( ddsHeader.ddsPixelFormat.dwFourCC ).arg( "FourCC" ); + qCCritical( nsIo ) << QObject::tr( "Unsupported DDS format: %1 %2" ).arg( ddsHeader.ddspf.dwFourCC ).arg( "FourCC" ); return false; break; } } else { //qDebug() << "RAW"; - // switch on BPP - //nif->set( iData, "Pixel Format", 0 ); - switch ( ddsHeader.ddsPixelFormat.dwBPP ) { + switch ( ddsHeader.ddspf.dwRGBBitCount ) { case 24: // RGB nif->set( iData, "Pixel Format", 0 ); @@ -2203,32 +1801,32 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) break; default: // theoretically could have a palettised DDS in 8bpp - qCCritical( nsIo ) << QObject::tr( "Unsupported DDS format: %1 %2" ).arg( ddsHeader.ddsPixelFormat.dwBPP ).arg( "BPP" ); + qCCritical( nsIo ) << QObject::tr( "Unsupported DDS format: %1 %2" ).arg( ddsHeader.ddspf.dwRGBBitCount ).arg( "BPP" ); return false; break; } } - qDebug() << "BPP:" << ddsHeader.ddsPixelFormat.dwBPP; - qDebug() << "RMask:" << ddsHeader.ddsPixelFormat.dwRMask; - qDebug() << "GMask:" << ddsHeader.ddsPixelFormat.dwGMask; - qDebug() << "BMask:" << ddsHeader.ddsPixelFormat.dwBMask; - qDebug() << "AMask:" << ddsHeader.ddsPixelFormat.dwAMask; + qDebug() << "BPP:" << ddsHeader.ddspf.dwRGBBitCount; + qDebug() << "RMask:" << ddsHeader.ddspf.dwRBitMask; + qDebug() << "GMask:" << ddsHeader.ddspf.dwGBitMask; + qDebug() << "BMask:" << ddsHeader.ddspf.dwBBitMask; + qDebug() << "AMask:" << ddsHeader.ddspf.dwABitMask; // Note that these might not match what's expected; hopefully the loader function is smart if ( nif->checkVersion( 0, 0x0A020000 ) ) { - nif->set( iData, "Bits Per Pixel", ddsHeader.ddsPixelFormat.dwBPP ); - nif->set( iData, "Red Mask", ddsHeader.ddsPixelFormat.dwRMask ); - nif->set( iData, "Green Mask", ddsHeader.ddsPixelFormat.dwGMask ); - nif->set( iData, "Blue Mask", ddsHeader.ddsPixelFormat.dwBMask ); - nif->set( iData, "Alpha Mask", ddsHeader.ddsPixelFormat.dwAMask ); + nif->set( iData, "Bits Per Pixel", ddsHeader.ddspf.dwRGBBitCount ); + nif->set( iData, "Red Mask", ddsHeader.ddspf.dwRBitMask ); + nif->set( iData, "Green Mask", ddsHeader.ddspf.dwGBitMask ); + nif->set( iData, "Blue Mask", ddsHeader.ddspf.dwBBitMask ); + nif->set( iData, "Alpha Mask", ddsHeader.ddspf.dwABitMask ); QModelIndex oldFastCompare = nif->getIndex( iData, "Old Fast Compare" ); for ( int i = 0; i < 8; i++ ) { - if ( ddsHeader.ddsPixelFormat.dwBPP == 24 ) { + if ( ddsHeader.ddspf.dwRGBBitCount == 24 ) { nif->set( oldFastCompare.child( i, 0 ), unk8bytes24[i] ); - } else if ( ddsHeader.ddsPixelFormat.dwBPP == 32 ) { + } else if ( ddsHeader.ddspf.dwRGBBitCount == 32 ) { nif->set( oldFastCompare.child( i, 0 ), unk8bytes32[i] ); } } @@ -2239,7 +1837,7 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) QModelIndex destChannels = nif->getIndex( iData, "Channels" ); // DXT1, DXT5 - if ( ddsHeader.ddsPixelFormat.dwFlags & DDPF_FOURCC ) { + if ( ddsHeader.ddspf.dwFlags & DDS_FOURCC ) { // compressed nif->set( iData, "Bits Per Pixel", 0 ); @@ -2256,17 +1854,17 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) nif->set( destChannels.child( i, 0 ), "Is Signed", 1 ); } } else { - nif->set( iData, "Bits Per Pixel", ddsHeader.ddsPixelFormat.dwBPP ); + nif->set( iData, "Bits Per Pixel", ddsHeader.ddspf.dwRGBBitCount ); // set RGB mask separately for ( int i = 0; i < 3; i++ ) { - if ( ddsHeader.ddsPixelFormat.dwRMask == RGBA_INV_MASK[i] ) { + if ( ddsHeader.ddspf.dwRBitMask == RGBA_INV_MASK[i] ) { //qDebug() << "red channel" << i; nif->set( destChannels.child( i, 0 ), "Type", 0 ); - } else if ( ddsHeader.ddsPixelFormat.dwGMask == RGBA_INV_MASK[i] ) { + } else if ( ddsHeader.ddspf.dwGBitMask == RGBA_INV_MASK[i] ) { //qDebug() << "green channel" << i; nif->set( destChannels.child( i, 0 ), "Type", 1 ); - } else if ( ddsHeader.ddsPixelFormat.dwBMask == RGBA_INV_MASK[i] ) { + } else if ( ddsHeader.ddspf.dwBBitMask == RGBA_INV_MASK[i] ) { //qDebug() << "blue channel" << i; nif->set( destChannels.child( i, 0 ), "Type", 2 ); } @@ -2278,12 +1876,12 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) nif->set( destChannels.child( i, 0 ), "Is Signed", 0 ); } - if ( ddsHeader.ddsPixelFormat.dwBPP == 32 ) { + if ( ddsHeader.ddspf.dwRGBBitCount == 32 ) { nif->set( destChannels.child( 3, 0 ), "Type", 3 ); // alpha nif->set( destChannels.child( 3, 0 ), "Convention", 0 ); // fixed nif->set( destChannels.child( 3, 0 ), "Bits Per Channel", 8 ); nif->set( destChannels.child( 3, 0 ), "Is Signed", 0 ); - } else if ( ddsHeader.ddsPixelFormat.dwBPP == 24 ) { + } else if ( ddsHeader.ddspf.dwRGBBitCount == 24 ) { nif->set( destChannels.child( 3, 0 ), "Type", 19 ); // empty nif->set( destChannels.child( 3, 0 ), "Convention", 5 ); // empty nif->set( destChannels.child( 3, 0 ), "Bits Per Channel", 0 ); @@ -2298,7 +1896,7 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) QModelIndex destMipMaps = nif->getIndex( iData, "Mipmaps" ); nif->updateArray( destMipMaps ); - nif->set( iData, "Bytes Per Pixel", ddsHeader.ddsPixelFormat.dwBPP / 8 ); + nif->set( iData, "Bytes Per Pixel", ddsHeader.ddspf.dwRGBBitCount / 8 ); int mipmapWidth = ddsHeader.dwWidth; int mipmapHeight = ddsHeader.dwHeight; @@ -2309,15 +1907,15 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) nif->set( destMipMaps.child( i, 0 ), "Height", mipmapHeight ); nif->set( destMipMaps.child( i, 0 ), "Offset", mipmapOffset ); - if ( ddsHeader.ddsPixelFormat.dwFlags & DDPF_FOURCC ) { - if ( ddsHeader.ddsPixelFormat.dwFourCC == FOURCC_DXT1 ) { + if ( ddsHeader.ddspf.dwFlags & DDS_FOURCC ) { + if ( ddsHeader.ddspf.dwFourCC == FOURCC_DXT1 ) { mipmapOffset += std::max( 8, ( mipmapWidth * mipmapHeight / 2 ) ); - } else if ( ddsHeader.ddsPixelFormat.dwFourCC == FOURCC_DXT5 ) { + } else if ( ddsHeader.ddspf.dwFourCC == FOURCC_DXT5 ) { mipmapOffset += std::max( 16, ( mipmapWidth * mipmapHeight ) ); } - } else if ( ddsHeader.ddsPixelFormat.dwBPP == 24 ) { + } else if ( ddsHeader.ddspf.dwRGBBitCount == 24 ) { mipmapOffset += ( mipmapWidth * mipmapHeight * 3 ); - } else if ( ddsHeader.ddsPixelFormat.dwBPP == 32 ) { + } else if ( ddsHeader.ddspf.dwRGBBitCount == 32 ) { mipmapOffset += ( mipmapWidth * mipmapHeight * 4 ); } diff --git a/src/gl/gltexloaders.h b/src/gl/gltexloaders.h index 37cfe38a6..456debc2a 100644 --- a/src/gl/gltexloaders.h +++ b/src/gl/gltexloaders.h @@ -33,15 +33,19 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef GLTEXLOADERS_H #define GLTEXLOADERS_H -#include - +#include "gli.hpp" +class QByteArray; class QModelIndex; class QString; typedef unsigned int GLuint; typedef unsigned int GLenum; +extern GLuint GLI_create_texture( gli::texture& texture, GLenum& target, GLuint& id ); +extern GLuint GLI_create_texture_fallback( gli::texture& texture, GLenum & target, GLuint& id ); +extern gli::texture load_if_valid( const char * data, int size ); + //! @file gltexloaders.h Texture loading functions header /*! A function for loading textures. @@ -75,7 +79,7 @@ extern bool texLoad( const QString & filepath, QString & format, GLenum & target * @param mipmaps Contains the number of mipmaps on successful load. * @return True if the load was successful, false otherwise. */ -extern bool texLoad( const QModelIndex & iData, QString & format, GLuint & width, GLuint & height, GLuint & mipmaps ); +extern bool texLoad( const QModelIndex & iData, QString & format, GLenum & target, GLuint & width, GLuint & height, GLuint & mipmaps, GLuint & id ); /*! A function which checks whether the given file can be loaded. * From 7ebc646151184711c0148e05a9f5176ede3a4595 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Wed, 6 Dec 2017 03:50:40 -0500 Subject: [PATCH 113/152] [GL] Do not attempt shader usage on non-Bethesda files For now, disable shaders on anything non-Bethesda (no User Version 2). Nothing meaningful could be added via shader without also requiring NifSkope updates to the renderer, specifically setupProgram, so may as well optimize this for now. This causes a significant FPS increase on non-Bethesda NIFs with no visual change, since there are no non-Bethesda shaders. --- src/gl/renderer.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/gl/renderer.cpp b/src/gl/renderer.cpp index dd17cf2c2..015639141 100644 --- a/src/gl/renderer.cpp +++ b/src/gl/renderer.cpp @@ -451,7 +451,9 @@ QString Renderer::setupProgram( Shape * mesh, const QString & hint ) PropertyList props; mesh->activeProperties( props ); - if ( !shader_ready || (mesh->scene->options & Scene::DisableShaders) || (mesh->scene->visMode & Scene::VisSilhouette) ) { + if ( !shader_ready || (mesh->scene->options & Scene::DisableShaders) + || (mesh->scene->visMode & Scene::VisSilhouette) + || (mesh->nifVersion == 0) ) { setupFixedFunction( mesh, props ); return QString( "fixed function pipeline" ); } From 3c10265390039f502d797ccf4ef52fd3c3ba023f Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Wed, 6 Dec 2017 04:18:53 -0500 Subject: [PATCH 114/152] [NIF] 20.3.1.2 custom RTTI hash support There are some games with a custom NIF version that has no RTTI strings in the header (Block Types) but instead a list of hashes. It was fairly nonintrusive to implement support for this format as you simply look the block up by hash instead of by name. The hashes are added when the XML is read. The hash used is a version of djbhash. --- src/model/basemodel.cpp | 11 +++++++++++ src/model/basemodel.h | 5 +++++ src/model/nifmodel.cpp | 35 ++++++++++++++++++++++++++++++++--- src/model/nifmodel.h | 1 + src/xml/nifxml.cpp | 2 ++ 5 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/model/basemodel.cpp b/src/model/basemodel.cpp index 686ed2a83..745315656 100644 --- a/src/model/basemodel.cpp +++ b/src/model/basemodel.cpp @@ -874,3 +874,14 @@ QVariant BaseModelEval::operator()(const QVariant & v) const return v; } + +unsigned DJB1Hash( const char * key, unsigned tableSize ) +{ + unsigned hash = 0; + while ( *key ) { + hash *= 33; + hash += *key; + key++; + } + return hash % tableSize; +} diff --git a/src/model/basemodel.h b/src/model/basemodel.h index fa1b2bdf7..58cd9afa1 100644 --- a/src/model/basemodel.h +++ b/src/model/basemodel.h @@ -43,9 +43,14 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include + #define NifSkopeDisplayRole (Qt::UserRole + 42) +// Used for Block Name hashing +unsigned DJB1Hash( const char * key, unsigned tableSize = UINT_MAX ); + //! @file basemodel.h BaseModel, BaseModelEval class TestMessage; diff --git a/src/model/nifmodel.cpp b/src/model/nifmodel.cpp index e7b16112d..3e1166e42 100644 --- a/src/model/nifmodel.cpp +++ b/src/model/nifmodel.cpp @@ -315,9 +315,13 @@ void NifModel::updateHeader() NifItem * idxBlockTypes = getItem( header, "Block Types" ); NifItem * idxBlockTypeIndices = getItem( header, "Block Type Index" ); NifItem * idxBlockSize = getItem( header, "Block Size" ); + // 20.3.1.2 Custom Version + NifItem * idxBlockTypeHashes = nullptr; + if ( version == 0x14030102 ) + idxBlockTypeHashes = getItem( header, "Block Type Hashes" ); // Update Block Types, Block Type Index, and Block Size - if ( idxBlockTypes && idxBlockTypeIndices ) { + if ( (idxBlockTypes || idxBlockTypeHashes) && idxBlockTypeIndices ) { QVector blocktypes; QVector blocktypeindices; QVector blocksizes; @@ -349,7 +353,8 @@ void NifModel::updateHeader() set( header, "Num Block Types", blocktypes.count() ); - updateArrayItem( idxBlockTypes ); + if ( idxBlockTypes ) + updateArrayItem( idxBlockTypes ); updateArrayItem( idxBlockTypeIndices ); if ( version >= 0x14020000 && idxBlockSize ) { @@ -357,10 +362,21 @@ void NifModel::updateHeader() } setState( Processing ); - idxBlockTypes->setArray( blocktypes ); idxBlockTypeIndices->setArray( blocktypeindices ); + if ( idxBlockTypes ) + idxBlockTypes->setArray( blocktypes ); if ( blocksizes.count() ) idxBlockSize->setArray( blocksizes ); + // 20.3.1.2 Custom Version + if ( idxBlockTypeHashes ) { + QVector blocktypehashes; + for ( const auto & t : blocktypes ) + blocktypehashes << DJB1Hash( t.toStdString().c_str() ); + + updateArrayItem( idxBlockTypeHashes ); + idxBlockTypeHashes->setArray( blocktypehashes ); + } + restoreState(); // For 20.1 and above strings are saved in the header. Max String Length must be updated. @@ -1778,6 +1794,19 @@ bool NifModel::load( QIODevice & device ) // the upper bit or the blocktypeindex seems to be related to PhysX int blktypidx = get( index( c, 0, getIndex( createIndex( header->row(), 0, header ), "Block Type Index" ) ) ); blktyp = get( index( blktypidx & 0x7FFF, 0, getIndex( createIndex( header->row(), 0, header ), "Block Types" ) ) ); + + // 20.3.1.2 Custom Version + if ( version == 0x14030102 ) { + auto hash = get( + index( blktypidx & 0x7FFF, 0, getIndex( createIndex( header->row(), 0, header ), + "Block Type Hashes" ) ) + ); + + if ( blockHashes.contains( hash ) ) + blktyp = blockHashes[hash]->id; + else + throw tr( "Block Hash not found." ); + } // note: some 10.0.1.0 version nifs from Oblivion in certain distributions seem to be missing // these four bytes on the havok blocks diff --git a/src/model/nifmodel.h b/src/model/nifmodel.h index 02a10be02..d65861dc1 100644 --- a/src/model/nifmodel.h +++ b/src/model/nifmodel.h @@ -367,6 +367,7 @@ public slots: static QHash compounds; static QHash fixedCompounds; static QHash blocks; + static QMap blockHashes; private: struct Settings diff --git a/src/xml/nifxml.cpp b/src/xml/nifxml.cpp index a6fa5d713..37e6a8bdc 100644 --- a/src/xml/nifxml.cpp +++ b/src/xml/nifxml.cpp @@ -49,6 +49,7 @@ QList NifModel::supportedVersions; QHash NifModel::compounds; QHash NifModel::fixedCompounds; QHash NifModel::blocks; +QMap NifModel::blockHashes; //! Parses nif.xml class NifXmlHandler final : public QXmlDefaultHandler @@ -445,6 +446,7 @@ class NifXmlHandler final : public QXmlDefaultHandler break; case tagBlock: NifModel::blocks.insert( blk->id, blk ); + NifModel::blockHashes.insert( DJB1Hash(blk->id.toStdString().c_str()), blk ); break; default: break; From b17571686f35f9611f534f06de34f53a1d99436a Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Wed, 6 Dec 2017 04:53:10 -0500 Subject: [PATCH 115/152] [GL] Skinning Improvements The geometry displayed for skinned NiTri-based objects always came from the triangles on the shape. However for shapes with a partition, the Triangles array is completely optional on the shape. This is present in some Skyrim NIFs, so these NIFs do not render. These changes make it so that the geometry will switch between the shape geometry and the partition geometry with the Skinning toggle. Now any shapes with only partition geometry will show up, and correctly disappear when turning off Skinning. This will also show any problems with partitions that might go uncaught because it was showing the wrong geometry. Also adds a spell to update the shape triangles from the partition triangles. --- src/gl/bsshape.cpp | 27 ++++++------ src/gl/glmesh.cpp | 101 ++++++++++++++++++++++++++------------------ src/gl/glmesh.h | 4 +- src/gl/gltools.cpp | 33 +++++++++++---- src/gl/gltools.h | 3 ++ src/nifskope_ui.cpp | 1 + src/spells/mesh.cpp | 31 ++++++++++++++ src/spells/mesh.h | 11 +++++ 8 files changed, 149 insertions(+), 62 deletions(-) diff --git a/src/gl/bsshape.cpp b/src/gl/bsshape.cpp index 181381b6e..1c885647d 100644 --- a/src/gl/bsshape.cpp +++ b/src/gl/bsshape.cpp @@ -219,7 +219,7 @@ void BSShape::transform() if ( updateSkin ) { updateSkin = false; - doSkinning = false; + isSkinned = false; bones.clear(); weights.clear(); @@ -251,7 +251,11 @@ void BSShape::transform() } } - doSkinning = weights.count(); + auto b = nif->getIndex( iSkinData, "Bone List" ); + for ( int i = 0; i < weights.count(); i++ ) + weights[i].setTransform( nif, b.child( i, 0 ) ); + + isSkinned = weights.count(); } } @@ -273,21 +277,20 @@ void BSShape::transformShapes() transformRigid = true; - if ( doSkinning && scene->options & Scene::DoSkinning ) { + if ( isSkinned && scene->options & Scene::DoSkinning ) { transformRigid = false; - transVerts.resize( verts.count() ); + int vcnt = verts.count(); + + transVerts.resize( vcnt ); transVerts.fill( Vector3() ); - transNorms.resize( verts.count() ); + transNorms.resize( vcnt ); transNorms.fill( Vector3() ); - transTangents.resize( verts.count() ); + transTangents.resize( vcnt ); transTangents.fill( Vector3() ); - transBitangents.resize( verts.count() ); + transBitangents.resize( vcnt ); transBitangents.fill( Vector3() ); - auto b = nif->getIndex( iSkinData, "Bone List" ); - for ( int i = 0; i < weights.count(); i++ ) - weights[i].setTransform( nif, b.child( i, 0 ) ); Node * root = findParent( 0 ); for ( const BoneWeights & bw : weights ) { @@ -295,7 +298,7 @@ void BSShape::transformShapes() if ( bone ) { Transform t = scene->view * bone->localTrans( 0 ) * bw.trans; for ( const VertexWeight & w : bw.weights ) { - if ( w.vertex >= verts.count() ) + if ( w.vertex >= vcnt ) continue; transVerts[w.vertex] += t * verts[w.vertex] * w.weight; @@ -306,7 +309,7 @@ void BSShape::transformShapes() } } - for ( int n = 0; n < verts.count(); n++ ) { + for ( int n = 0; n < vcnt; n++ ) { transNorms[n].normalize(); transTangents[n].normalize(); transBitangents[n].normalize(); diff --git a/src/gl/glmesh.cpp b/src/gl/glmesh.cpp index ae9e64397..9ddbea090 100644 --- a/src/gl/glmesh.cpp +++ b/src/gl/glmesh.cpp @@ -181,7 +181,14 @@ void Mesh::update( const NifModel * nif, const QModelIndex & index ) { Node::update( nif, index ); - if ( !iBlock.isValid() || !index.isValid() ) + // Was Skinning toggled? + // If so, switch between partition triangles and data triangles + bool doSkinningCurr = (scene->options & Scene::DoSkinning); + updateSkin |= (doSkinning != doSkinningCurr) && nif->checkVersion( 0, 0x14040000 ); + updateData |= updateSkin; + doSkinning = doSkinningCurr; + + if ( !iBlock.isValid() || !index.isValid() && !updateSkin ) return; if ( !isBSLODPresent ) { @@ -197,6 +204,7 @@ void Mesh::update( const NifModel * nif, const QModelIndex & index ) updateSkin |= ( iSkin == index ); updateSkin |= ( iSkinData == index ); updateSkin |= ( iSkinPart == index ); + updateSkin |= ( updateData && isSkinned && doSkinning ); if ( nif->checkVersion( 0x14050000, 0 ) && nif->inherits( iBlock, "NiMesh" ) ) updateSkin = false; @@ -642,10 +650,15 @@ void Mesh::transform() if ( updateSkin ) { updateSkin = false; + isSkinned = false; weights.clear(); partitions.clear(); iSkinData = nif->getBlock( nif->getLink( iSkin, "Data" ), "NiSkinData" ); + iSkinPart = nif->getBlock( nif->getLink( iSkin, "Skin Partition" ), "NiSkinPartition" ); + if ( !iSkinPart.isValid() ) + // nif versions < 10.2.0.0 have skin partition linked in the skin data block + iSkinPart = nif->getBlock( nif->getLink( iSkinData, "Skin Partition" ), "NiSkinPartition" ); skeletonRoot = nif->getLink( iSkin, "Skeleton Root" ); skeletonTrans = Transform( nif, iSkinData ); @@ -653,28 +666,41 @@ void Mesh::transform() bones = nif->getLinkArray( iSkin, "Bones" ); QModelIndex idxBones = nif->getIndex( iSkinData, "Bone List" ); - unsigned char hvw = nif->get( iSkinData, "Has Vertex Weights" ); - int vcnt = hvw ? 0 : verts.count(); - - if ( idxBones.isValid() /*&& hvw*/ ) { + if ( idxBones.isValid() ) { + bool hvw = nif->get( iSkinData, "Has Vertex Weights" ); + // Ignore weights listed in NiSkinData if NiSkinPartition exists + hvw = hvw && !iSkinPart.isValid(); + int vcnt = hvw ? 0 : verts.count(); for ( int b = 0; b < nif->rowCount( idxBones ) && b < bones.count(); b++ ) { weights.append( BoneWeights( nif, idxBones.child( b, 0 ), bones[ b ], vcnt ) ); } } - iSkinPart = nif->getBlock( nif->getLink( iSkin, "Skin Partition" ), "NiSkinPartition" ); - - if ( !iSkinPart.isValid() ) - // nif versions < 10.2.0.0 have skin partition linked in the skin data block - iSkinPart = nif->getBlock( nif->getLink( iSkinData, "Skin Partition" ), "NiSkinPartition" ); - - if ( iSkinPart.isValid() ) { + if ( iSkinPart.isValid() && doSkinning ) { QModelIndex idx = nif->getIndex( iSkinPart, "Skin Partition Blocks" ); + uint numTris = 0; + uint numStrips = 0; for ( int i = 0; i < nif->rowCount( idx ) && idx.isValid(); i++ ) { partitions.append( SkinPartition( nif, idx.child( i, 0 ) ) ); + numTris += partitions[i].triangles.size(); + numStrips += partitions[i].tristrips.size(); + } + + triangles.clear(); + tristrips.clear(); + + triangles.reserve( numTris ); + tristrips.reserve( numStrips ); + + for ( const SkinPartition& part : partitions ) { + triangles << part.getRemappedTriangles(); + tristrips << part.getRemappedTristrips(); } } + + isSkinned = weights.count() || partitions.count(); + } Node::transform(); @@ -689,22 +715,24 @@ void Mesh::transformShapes() transformRigid = true; - if ( weights.count() && (scene->options & Scene::DoSkinning) ) { + if ( isSkinned && doSkinning ) { transformRigid = false; - transVerts.resize( verts.count() ); + int vcnt = verts.count(); + + transVerts.resize( vcnt ); transVerts.fill( Vector3() ); - transNorms.resize( norms.count() ); + transNorms.resize( vcnt ); transNorms.fill( Vector3() ); - transTangents.resize( tangents.count() ); + transTangents.resize( vcnt ); transTangents.fill( Vector3() ); - transBitangents.resize( bitangents.count() ); + transBitangents.resize( vcnt ); transBitangents.fill( Vector3() ); Node * root = findParent( skeletonRoot ); if ( partitions.count() ) { - foreach ( const SkinPartition part, partitions ) { + for ( const SkinPartition& part : partitions ) { QVector boneTrans( part.boneMap.count() ); for ( int t = 0; t < boneTrans.count(); t++ ) { @@ -719,33 +747,27 @@ void Mesh::transformShapes() for ( int v = 0; v < part.vertexMap.count(); v++ ) { int vindex = part.vertexMap[ v ]; - - if ( vindex < 0 || vindex > transVerts.count() ) + if ( vindex < 0 || vindex >= vcnt ) break; if ( transVerts[vindex] == Vector3() ) { for ( int w = 0; w < part.numWeightsPerVertex; w++ ) { QPair weight = part.weights[ v * part.numWeightsPerVertex + w ]; - Transform trans = boneTrans.value( weight.first ); - - if ( verts.count() > vindex ) - transVerts[vindex] += trans * verts[ vindex ] * weight.second; - if ( norms.count() > vindex ) - transNorms[vindex] += trans.rotation * norms[ vindex ] * weight.second; - if ( tangents.count() > vindex ) - transTangents[vindex] += trans.rotation * tangents[ vindex ] * weight.second; + Transform trans = boneTrans.value( weight.first ); - if ( bitangents.count() > vindex ) - transBitangents[vindex] += trans.rotation * bitangents[ vindex ] * weight.second; + transVerts[vindex] += trans * verts[ vindex ] * weight.second; + transNorms[vindex] += trans.rotation * norms[ vindex ] * weight.second; + transTangents[vindex] += trans.rotation * tangents[ vindex ] * weight.second; + transBitangents[vindex] += trans.rotation * bitangents[ vindex ] * weight.second; } } } } } else { int x = 0; - foreach ( const BoneWeights bw, weights ) { + for ( const BoneWeights& bw : weights ) { Transform trans = viewTrans() * skeletonTrans; Node * bone = root ? root->findChild( bw.bone ) : 0; @@ -759,17 +781,14 @@ void Mesh::transformShapes() Matrix natrix = trans.rotation; for ( const VertexWeight& vw : bw.weights ) { - if ( transVerts.count() > vw.vertex ) - transVerts[ vw.vertex ] += trans * verts[ vw.vertex ] * vw.weight; - - if ( transNorms.count() > vw.vertex ) - transNorms[ vw.vertex ] += natrix * norms[ vw.vertex ] * vw.weight; - - if ( transTangents.count() > vw.vertex ) - transTangents[ vw.vertex ] += natrix * tangents[ vw.vertex ] * vw.weight; + int vindex = vw.vertex; + if ( vindex < 0 || vindex >= vcnt ) + break; - if ( transBitangents.count() > vw.vertex ) - transBitangents[ vw.vertex ] += natrix * bitangents[ vw.vertex ] * vw.weight; + transVerts[vindex] += trans * verts[vindex] * vw.weight; + transNorms[vindex] += natrix * norms[vindex] * vw.weight; + transTangents[vindex] += natrix * tangents[vindex] * vw.weight; + transBitangents[vindex] += natrix * bitangents[vindex] * vw.weight; } } } diff --git a/src/gl/glmesh.h b/src/gl/glmesh.h index db9c9b493..befac098a 100644 --- a/src/gl/glmesh.h +++ b/src/gl/glmesh.h @@ -74,6 +74,8 @@ class Shape : public Node QPersistentModelIndex iData; //! Does the data need updating? bool updateData = false; + //! Was Skinning enabled last update? + bool doSkinning = false; //! Skin instance QPersistentModelIndex iSkin; @@ -121,7 +123,7 @@ class Shape : public Node //! Does the skin data need updating? bool updateSkin = false; //! Toggle for skinning - bool doSkinning = false; + bool isSkinned = false; int skeletonRoot = 0; Transform skeletonTrans; diff --git a/src/gl/gltools.cpp b/src/gl/gltools.cpp index 1531e0ea8..05ed7a92d 100644 --- a/src/gl/gltools.cpp +++ b/src/gl/gltools.cpp @@ -55,19 +55,12 @@ BoneWeights::BoneWeights( const NifModel * nif, const QModelIndex & index, int b bone = b; QModelIndex idxWeights = nif->getIndex( index, "Vertex Weights" ); - - if ( idxWeights.isValid() ) { + if ( vcnt && idxWeights.isValid() ) { for ( int c = 0; c < nif->rowCount( idxWeights ); c++ ) { QModelIndex idx = idxWeights.child( c, 0 ); weights.append( VertexWeight( nif->get( idx, "Index" ), nif->get( idx, "Weight" ) ) ); } - } else { - // create artificial ones, TODO: should they weight nothing* instead? - for ( int c = 0; c < vcnt; c++ ) - weights.append( VertexWeight( c, 1.0f ) ); } - - } void BoneWeights::setTransform( const NifModel * nif, const QModelIndex & index ) @@ -118,6 +111,30 @@ SkinPartition::SkinPartition( const NifModel * nif, const QModelIndex & index ) triangles = nif->getArray( index, "Triangles" ); } +QVector SkinPartition::getRemappedTriangles() const +{ + QVector tris; + + for ( const auto& t : triangles ) + tris << Triangle( vertexMap[t.v1()], vertexMap[t.v2()], vertexMap[t.v3()] ); + + return tris; +} + +QVector> SkinPartition::getRemappedTristrips() const +{ + QVector> tris; + + for ( const auto& t : tristrips ) { + QVector points; + for ( const auto& p : t ) + points << vertexMap[p]; + tris << points; + } + + return tris; +} + /* * Bound Sphere */ diff --git a/src/gl/gltools.h b/src/gl/gltools.h index fbd8c71f5..853ca8fe0 100644 --- a/src/gl/gltools.h +++ b/src/gl/gltools.h @@ -101,6 +101,9 @@ class SkinPartition final SkinPartition() { numWeightsPerVertex = 0; } SkinPartition( const NifModel * nif, const QModelIndex & index ); + QVector getRemappedTriangles() const; + QVector> getRemappedTristrips() const; + QVector boneMap; QVector vertexMap; diff --git a/src/nifskope_ui.cpp b/src/nifskope_ui.cpp index 5d3d3a8ad..5ddade129 100644 --- a/src/nifskope_ui.cpp +++ b/src/nifskope_ui.cpp @@ -246,6 +246,7 @@ void NifSkope::initActions() ui->aShowConstraints, ui->aShowMarkers, ui->aShowHidden, ui->aDoSkinning }, false ); connect( showActions, &QActionGroup::triggered, ogl->getScene(), &Scene::updateSceneOptionsGroup ); + connect( showActions, &QActionGroup::triggered, ogl, &GLView::updateScene ); shadingActions = agroup( { ui->aTextures, ui->aVertexColors, ui->aSpecular, ui->aGlow, ui->aCubeMapping, ui->aLighting, ui->aDisableShading }, false ); connect( shadingActions, &QActionGroup::triggered, ogl->getScene(), &Scene::updateSceneOptionsGroup ); diff --git a/src/spells/mesh.cpp b/src/spells/mesh.cpp index 1336185fb..741a9ea1a 100644 --- a/src/spells/mesh.cpp +++ b/src/spells/mesh.cpp @@ -757,3 +757,34 @@ class spUpdateAllBounds final : public Spell }; REGISTER_SPELL( spUpdateAllBounds ) + + +//! Update Triangles on Data from Skin +bool spUpdateTrianglesFromSkin::isApplicable( const NifModel * nif, const QModelIndex & index ) +{ + return nif->isNiBlock( index, "NiTriShape" ) && nif->getLink( index, "Skin Instance" ) != -1; +} + +QModelIndex spUpdateTrianglesFromSkin::cast( NifModel * nif, const QModelIndex & index ) +{ + auto iData = nif->getBlock( nif->getLink( index, "Data" ) ); + auto iSkin = nif->getBlock( nif->getLink( index, "Skin Instance" ) ); + auto iSkinPart = nif->getBlock( nif->getLink( iSkin, "Skin Partition" ) ); + if ( !iSkinPart.isValid() || !iData.isValid() ) + return QModelIndex(); + + QVector tris; + auto iParts = nif->getIndex( iSkinPart, "Skin Partition Blocks" ); + for ( int i = 0; i < nif->rowCount( iParts ) && iParts.isValid(); i++ ) + tris << SkinPartition( nif, iParts.child( i, 0 ) ).getRemappedTriangles(); + + nif->set( iData, "Has Triangles", true ); + nif->set( iData, "Num Triangles", tris.size() ); + nif->set( iData, "Num Triangle Points", tris.size() * 3 ); + nif->updateArray( iData, "Triangles" ); + nif->setArray( iData, "Triangles", tris ); + + return index; +} + +REGISTER_SPELL( spUpdateTrianglesFromSkin ) diff --git a/src/spells/mesh.h b/src/spells/mesh.h index fb99564ae..7da9c452a 100644 --- a/src/spells/mesh.h +++ b/src/spells/mesh.h @@ -17,4 +17,15 @@ class spUpdateCenterRadius final : public Spell QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final; }; +//! Update Triangles on Data from Skin +class spUpdateTrianglesFromSkin final : public Spell +{ +public: + QString name() const override final { return Spell::tr( "Update Triangles From Skin" ); } + QString page() const override final { return Spell::tr( "Mesh" ); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final; + QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final; +}; + #endif From ae5307a725c629acf425478e6d2ed67a948d06e5 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Thu, 7 Dec 2017 16:44:39 -0500 Subject: [PATCH 116/152] [GL] Correct 20.1+ UV Set selection, 20.5+ Anisotropy for NiTexturingProperty The UV index for maps in NiTexturingProperty was moved to the flags value's lower byte. The code was only dealing with the clamp and filter modes in the upper byte. This broke light maps and other secondary maps that used multiple texcoords. Moved anistropy management to glproperty and added support for Max Anisotropy value in TexDesc. It will now override the Anisotropic Filtering user setting for that texture. Also modified the Flags UI for TexDesc to more easily edit the UV Index. --- src/gl/glproperty.cpp | 22 +++++++++++++++++++--- src/gl/glproperty.h | 1 + src/gl/gltex.cpp | 9 ++++++--- src/gl/gltex.h | 2 ++ src/spells/flags.cpp | 4 ++++ 5 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/gl/glproperty.cpp b/src/gl/glproperty.cpp index 0d7fbbdf9..f9332c530 100644 --- a/src/gl/glproperty.cpp +++ b/src/gl/glproperty.cpp @@ -332,14 +332,28 @@ void TexturingProperty::update( const NifModel * nif, const QModelIndex & proper textures[t].coordset = nif->get( iTex, "UV Set" ); int filterMode = 0, clampMode = 0; - if ( nif->checkVersion( 0, 0x14000005 ) ) { + if ( nif->checkVersion( 0, 0x14010002 ) ) { filterMode = nif->get( iTex, "Filter Mode" ); clampMode = nif->get( iTex, "Clamp Mode" ); } else if ( nif->checkVersion( 0x14010003, 0 ) ) { - filterMode = ( ( nif->get( iTex, "Flags" ) & 0x0F00 ) >> 0x08 ); - clampMode = ( ( nif->get( iTex, "Flags" ) & 0xF000 ) >> 0x0C ); + auto flags = nif->get( iTex, "Flags" ); + filterMode = ((flags & 0x0F00) >> 0x08); + clampMode = ((flags & 0xF000) >> 0x0C); + textures[t].coordset = (flags & 0x00FF); } + float af = 1.0; + float max_af = get_max_anisotropy(); + // Let User Settings decide for trilinear + if ( filterMode == GL_LINEAR_MIPMAP_LINEAR ) + af = max_af; + + // Override with value in NIF for 20.5+ + if ( nif->checkVersion( 0x14050004, 0 ) ) + af = std::min( max_af, (float)nif->get( iTex, "Max Anisotropy" ) ); + + textures[t].maxAniso = std::max( 1.0f, std::min( af, max_af ) ); + // See OpenGL docs on glTexParameter and GL_TEXTURE_MIN_FILTER option // See also http://gregs-blog.com/2008/01/17/opengl-texture-filter-parameters-explained/ switch ( filterMode ) { @@ -419,6 +433,7 @@ bool TexturingProperty::bind( int id, const QString & fname ) if ( mipmaps == 0 ) return false; + glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, textures[id].maxAniso ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mipmaps > 1 ? textures[id].filter : GL_LINEAR ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, textures[id].wrapS ); @@ -871,6 +886,7 @@ bool BSShaderLightingProperty::bind( int id, const QString & fname, TexClampMode break; } + glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, get_max_anisotropy() ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mipmaps > 1 ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR ); glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); diff --git a/src/gl/glproperty.h b/src/gl/glproperty.h index 8a541321a..0fb318336 100644 --- a/src/gl/glproperty.h +++ b/src/gl/glproperty.h @@ -209,6 +209,7 @@ class TexturingProperty final : public Property GLenum filter = 0; GLint wrapS = 0, wrapT = 0; int coordset = 0; + float maxAniso = 1.0; bool hasTransform = false; diff --git a/src/gl/gltex.cpp b/src/gl/gltex.cpp index 1bf064b9b..d94a37a5f 100644 --- a/src/gl/gltex.cpp +++ b/src/gl/gltex.cpp @@ -64,7 +64,7 @@ PFNGLCLIENTACTIVETEXTUREARBPROC glClientActiveTextureARB = nullptr; GLint num_texture_units = 0; //! Maximum anisotropy -static float max_anisotropy = 1.0f; +float max_anisotropy = 1.0f; void set_max_anisotropy() { static QSettings settings; @@ -72,6 +72,11 @@ void set_max_anisotropy() max_anisotropy ); } +float get_max_anisotropy() +{ + return max_anisotropy; +} + void initializeTextureUnits( const QOpenGLContext * context ) { if ( context->hasExtension( "GL_ARB_multitexture" ) ) { @@ -394,7 +399,6 @@ int TexCache::bind( const QString & fname ) tx->target = GL_TEXTURE_2D; glBindTexture( tx->target, tx->id ); - glTexParameterf( tx->target, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy ); return tx->mipmaps; } @@ -427,7 +431,6 @@ int TexCache::bind( const QModelIndex & iSource ) } glBindTexture( GL_TEXTURE_2D, tx->id ); - glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy ); return tx->mipmaps; } diff --git a/src/gl/gltex.h b/src/gl/gltex.h index 8478b04e4..fca704e12 100644 --- a/src/gl/gltex.h +++ b/src/gl/gltex.h @@ -150,4 +150,6 @@ bool activateTextureUnit( int x ); // This is a problem only if a mesh uses all 9 texture slots void resetTextureUnits( int numTex = 8 ); +float get_max_anisotropy(); + #endif diff --git a/src/spells/flags.cpp b/src/spells/flags.cpp index b4e79c449..81481ec73 100644 --- a/src/spells/flags.cpp +++ b/src/spells/flags.cpp @@ -836,6 +836,9 @@ class spEditFlags : public Spell QVBoxLayout * vbox = new QVBoxLayout; dlg.setLayout( vbox ); + QSpinBox * spnUVIndex = dlgSpin( vbox, Spell::tr( "UV Index" ), 0, 0xFF ); + spnUVIndex->setValue( flags & 0x00FF ); + QStringList clampModes{ Spell::tr( "Clamp Both" ), Spell::tr( "Clamp S Wrap T" ), @@ -863,6 +866,7 @@ class spEditFlags : public Spell if ( dlg.exec() == QDialog::Accepted ) { flags = ( flags & 0x0FFF ) | ( cmbClamp->currentIndex() << 0x0C ); flags = ( flags & 0xF0FF ) | ( cmbFilter->currentIndex() << 0x08 ); + flags = ( flags & 0xFF00 ) | spnUVIndex->value(); nif->set( index, flags ); } } From 6e709f5b06e7d544abe44e6b1107e13873048492 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Thu, 7 Dec 2017 16:48:58 -0500 Subject: [PATCH 117/152] [GL] Fix loading of non-DDS textures Changes in the way textures were being loaded had to be updated for TGA, BMP, and embedded data as well. --- src/gl/gltex.cpp | 14 +++++++------- src/gl/gltexloaders.cpp | 22 +++++++++++++++------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/gl/gltex.cpp b/src/gl/gltex.cpp index d94a37a5f..28a1985f0 100644 --- a/src/gl/gltex.cpp +++ b/src/gl/gltex.cpp @@ -393,12 +393,12 @@ int TexCache::bind( const QString & fname ) watcher->addPath( tx->filepath ); tx->load(); - } - - if ( !tx->target ) - tx->target = GL_TEXTURE_2D; + } else { + if ( !tx->target ) + tx->target = GL_TEXTURE_2D; - glBindTexture( tx->target, tx->id ); + glBindTexture( tx->target, tx->id ); + } return tx->mipmaps; } @@ -428,10 +428,10 @@ int TexCache::bind( const QModelIndex & iSource ) catch ( QString & e ) { tx->status = e; } + } else { + glBindTexture( GL_TEXTURE_2D, tx->id ); } - glBindTexture( GL_TEXTURE_2D, tx->id ); - return tx->mipmaps; } } else if ( !nif->get( iSource, "File Name" ).isEmpty() ) { diff --git a/src/gl/gltexloaders.cpp b/src/gl/gltexloaders.cpp index 2997be8a4..e44b5b344 100644 --- a/src/gl/gltexloaders.cpp +++ b/src/gl/gltexloaders.cpp @@ -430,10 +430,13 @@ int texLoadPal( QIODevice & f, int width, int height, int num_mipmaps, int bpp, #define TGA_GREY_RLE 11 //! Load a TGA texture. -GLuint texLoadTGA( QIODevice & f, QString & texformat, GLuint & width, GLuint & height ) +GLuint texLoadTGA( QIODevice & f, QString & texformat, GLenum & target, GLuint & width, GLuint & height, GLuint & id ) { // see http://en.wikipedia.org/wiki/Truevision_TGA for a lot of this texformat = "TGA"; + target = GL_TEXTURE_2D; + + glBindTexture( target, id ); // read in tga header quint8 hdr[18]; @@ -568,7 +571,7 @@ quint16 get16( quint8 * x ) } //! Load a BMP texture. -GLuint texLoadBMP( QIODevice & f, QString & texformat, GLuint & width, GLuint & height ) +GLuint texLoadBMP( QIODevice & f, QString & texformat, GLenum & target, GLuint & width, GLuint & height, GLuint & id ) { // read in bmp header quint8 hdr[54]; @@ -578,6 +581,9 @@ GLuint texLoadBMP( QIODevice & f, QString & texformat, GLuint & width, GLuint & throw QString( "not a BMP file" ); texformat = "BMP"; + target = GL_TEXTURE_2D; + + glBindTexture( target, id ); width = get32( &hdr[18] ); height = get32( &hdr[22] ); @@ -822,9 +828,12 @@ bool texLoad( const QModelIndex & iData, QString & texformat, GLenum & target, G } //! Load NiPixelData or NiPersistentSrcTextureRendererData from a NifModel -GLuint texLoadNIF( QIODevice & f, QString & texformat, GLuint & width, GLuint & height, GLuint & id ) +GLuint texLoadNIF( QIODevice & f, QString & texformat, GLenum & target, GLuint & width, GLuint & height, GLuint & id ) { GLuint mipmaps = 0; + target = GL_TEXTURE_2D; + + glBindTexture( target, id ); NifModel pix; @@ -839,7 +848,6 @@ GLuint texLoadNIF( QIODevice & f, QString & texformat, GLuint & width, GLuint & if ( !iData.isValid() || iData == QModelIndex() ) throw QString( "this is not a normal .nif file; there should be only pixel data as root blocks" ); - GLenum target = 0; texLoad( iData, texformat, target, width, height, mipmaps, id ); } @@ -1131,11 +1139,11 @@ bool texLoad( const QString & filepath, QString & format, GLenum & target, GLuin if ( filepath.endsWith( ".dds", Qt::CaseInsensitive ) ) mipmaps = texLoadDDS( filepath, format, target, width, height, mipmaps, data, id ); else if ( filepath.endsWith( ".tga", Qt::CaseInsensitive ) ) - mipmaps = texLoadTGA( f, format, width, height ); + mipmaps = texLoadTGA( f, format, target, width, height, id ); else if ( filepath.endsWith( ".bmp", Qt::CaseInsensitive ) ) - mipmaps = texLoadBMP( f, format, width, height ); + mipmaps = texLoadBMP( f, format, target, width, height, id ); else if ( filepath.endsWith( ".nif", Qt::CaseInsensitive ) || filepath.endsWith( ".texcache", Qt::CaseInsensitive ) ) - mipmaps = texLoadNIF( f, format, width, height, id ); + mipmaps = texLoadNIF( f, format, target, width, height, id ); else isSupported = false; From 729e4fa361f41016b0880a4a77e0e6ca5096efa1 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Thu, 7 Dec 2017 16:54:55 -0500 Subject: [PATCH 118/152] [GL] QList to QVector for texcoords For several reasons the data that gets sent to the GPU should be contiguous, which QList is not. --- src/gl/glmesh.h | 2 +- src/gl/glproperty.cpp | 10 +++++----- src/gl/glproperty.h | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/gl/glmesh.h b/src/gl/glmesh.h index befac098a..98268234c 100644 --- a/src/gl/glmesh.h +++ b/src/gl/glmesh.h @@ -95,7 +95,7 @@ class Shape : public Node //! Bitangents QVector bitangents; //! UV coordinate sets - QList> coords; + QVector> coords; //! Triangles QVector triangles; //! Strip points diff --git a/src/gl/glproperty.cpp b/src/gl/glproperty.cpp index f9332c530..c7be99528 100644 --- a/src/gl/glproperty.cpp +++ b/src/gl/glproperty.cpp @@ -45,7 +45,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //! @file glproperty.cpp Encapsulation of NiProperty blocks defined in nif.xml //! Helper function that checks texture sets -bool checkSet( int s, const QList > & texcoords ) +bool checkSet( int s, const QVector > & texcoords ) { return s >= 0 && s < texcoords.count() && texcoords[s].count(); } @@ -463,7 +463,7 @@ bool TexturingProperty::bind( int id, const QString & fname ) return false; } -bool TexturingProperty::bind( int id, const QList > & texcoords ) +bool TexturingProperty::bind( int id, const QVector > & texcoords ) { if ( checkSet( textures[id].coordset, texcoords ) && bind( id ) ) { glEnable( GL_TEXTURE_2D ); @@ -476,7 +476,7 @@ bool TexturingProperty::bind( int id, const QList > & texcoords } } -bool TexturingProperty::bind( int id, const QList > & texcoords, int stage ) +bool TexturingProperty::bind( int id, const QVector > & texcoords, int stage ) { return ( activateTextureUnit( stage ) && bind( id, texcoords ) ); } @@ -574,7 +574,7 @@ bool TextureProperty::bind() return false; } -bool TextureProperty::bind( const QList > & texcoords ) +bool TextureProperty::bind( const QVector > & texcoords ) { if ( checkSet( 0, texcoords ) && bind() ) { glEnable( GL_TEXTURE_2D ); @@ -896,7 +896,7 @@ bool BSShaderLightingProperty::bind( int id, const QString & fname, TexClampMode return true; } -bool BSShaderLightingProperty::bind( int id, const QList > & texcoords ) +bool BSShaderLightingProperty::bind( int id, const QVector > & texcoords ) { if ( checkSet( 0, texcoords ) && bind( id ) ) { glEnable( GL_TEXTURE_2D ); diff --git a/src/gl/glproperty.h b/src/gl/glproperty.h index 0fb318336..0ceea9e89 100644 --- a/src/gl/glproperty.h +++ b/src/gl/glproperty.h @@ -231,8 +231,8 @@ class TexturingProperty final : public Property bool bind( int id, const QString & fname = QString() ); - bool bind( int id, const QList > & texcoords ); - bool bind( int id, const QList > & texcoords, int stage ); + bool bind( int id, const QVector > & texcoords ); + bool bind( int id, const QVector > & texcoords, int stage ); QString fileName( int id ) const; int coordSet( int id ) const; @@ -263,7 +263,7 @@ class TextureProperty final : public Property friend void glProperty( TextureProperty * ); bool bind(); - bool bind( const QList > & texcoords ); + bool bind( const QVector > & texcoords ); QString fileName() const; @@ -527,8 +527,8 @@ class BSShaderLightingProperty : public Property friend void glProperty( BSShaderLightingProperty * ); bool bind( int id, const QString & fname = QString(), TexClampMode mode = TexClampMode::WRAP_S_WRAP_T ); - bool bind( int id, const QList > & texcoords ); - bool bind( int id, const QList > & texcoords, int stage ); + bool bind( int id, const QVector > & texcoords ); + bool bind( int id, const QVector > & texcoords, int stage ); bool bindCube( int id, const QString & fname = QString() ); From f0c5c600f8f9edf69d6c1d134996e8ee4b9c69fb Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Fri, 8 Dec 2017 07:34:02 -0500 Subject: [PATCH 119/152] [GL] 20.1+ NiTri and NiMesh vertex color fixes Fixed the Source/Lighting mode reading for NiVertexColorProperty in 20.1+ so vertex colors render for those meshes now. Verified F_NORMUINT8_4_BGRA is indeed swizzled from RGBA and fixed that. Found games using NORMUINT8_4 for vertex colors instead of NORMUINT8_4_BGRA. However that particular game appears to use vertex colors in a non-standard manner and appears worse with VCs on. --- src/gl/glmesh.cpp | 14 +++++++++++++- src/gl/glproperty.cpp | 20 +++++++++++++------- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/gl/glmesh.cpp b/src/gl/glmesh.cpp index 9ddbea090..02cc588a8 100644 --- a/src/gl/glmesh.cpp +++ b/src/gl/glmesh.cpp @@ -404,6 +404,11 @@ void Mesh::transform() case 0x10: tempValue.changeType( NifValue::tByte ); break; + case 0x11: + if ( typeK == NiMesh::F_NORMUINT8_4 ) + tempValue.changeType( NifValue::tByteColor4 ); + typeLength = 1; + break; case 0x13: if ( typeK == NiMesh::F_NORMUINT8_4_BGRA ) tempValue.changeType( NifValue::tByteColor4 ); @@ -481,10 +486,17 @@ void Mesh::transform() coords[coordSet].append( tempValue.get() ); } break; - case NiMesh::F_NORMUINT8_4_BGRA: + case NiMesh::F_NORMUINT8_4: if ( compType == NiMesh::E_COLOR ) colors.append( tempValue.get() ); break; + case NiMesh::F_NORMUINT8_4_BGRA: + if ( compType == NiMesh::E_COLOR ) { + // Swizzle BGRA -> RGBA + auto c = tempValue.get().data(); + colors.append( {c[2], c[1], c[0], c[3]} ); + } + break; default: Message::append( abortMsg, QString( "[%1] Unsupported Component: %2" ).arg( stream ) .arg( NifValue::enumOptionName( "ComponentFormat", typeK ) ), diff --git a/src/gl/glproperty.cpp b/src/gl/glproperty.cpp index c7be99528..5763d5502 100644 --- a/src/gl/glproperty.cpp +++ b/src/gl/glproperty.cpp @@ -712,13 +712,19 @@ void VertexColorProperty::update( const NifModel * nif, const QModelIndex & bloc Property::update( nif, block ); if ( iBlock.isValid() && iBlock == block ) { - vertexmode = nif->get( iBlock, "Vertex Mode" ); - // 0 : source ignore - // 1 : source emissive - // 2 : source ambient + diffuse - lightmode = nif->get( iBlock, "Lighting Mode" ); - // 0 : emissive - // 1 : emissive + ambient + diffuse + if ( nif->checkVersion( 0, 0x14010001 ) ) { + vertexmode = nif->get( iBlock, "Vertex Mode" ); + // 0 : source ignore + // 1 : source emissive + // 2 : source ambient + diffuse + lightmode = nif->get( iBlock, "Lighting Mode" ); + // 0 : emissive + // 1 : emissive + ambient + diffuse + } else { + auto flags = nif->get( iBlock, "Flags" ); + vertexmode = (flags & 0x0030) >> 4; + lightmode = (flags & 0x0008) >> 3; + } } } From ed03e37d2f32004ab27dbe4044549cadd4712c75 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Fri, 8 Dec 2017 19:40:58 -0500 Subject: [PATCH 120/152] [GL] Fix regression for non-tangent meshes in b175716 Over-optimized a piece of code that didn't work for meshes without tangents/bitangents as I didn't test any meshes without them (which is very common in non-Bethesda NIFs). Also added the same checks to the non-partition part of the skinning. --- src/gl/glmesh.cpp | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/gl/glmesh.cpp b/src/gl/glmesh.cpp index 02cc588a8..4076f49ca 100644 --- a/src/gl/glmesh.cpp +++ b/src/gl/glmesh.cpp @@ -731,6 +731,9 @@ void Mesh::transformShapes() transformRigid = false; int vcnt = verts.count(); + int ncnt = norms.count(); + int tcnt = tangents.count(); + int bcnt = bitangents.count(); transVerts.resize( vcnt ); transVerts.fill( Vector3() ); @@ -769,10 +772,14 @@ void Mesh::transformShapes() Transform trans = boneTrans.value( weight.first ); - transVerts[vindex] += trans * verts[ vindex ] * weight.second; - transNorms[vindex] += trans.rotation * norms[ vindex ] * weight.second; - transTangents[vindex] += trans.rotation * tangents[ vindex ] * weight.second; - transBitangents[vindex] += trans.rotation * bitangents[ vindex ] * weight.second; + if ( vcnt > vindex ) + transVerts[vindex] += trans * verts[vindex] * weight.second; + if ( ncnt > vindex ) + transNorms[vindex] += trans.rotation * norms[vindex] * weight.second; + if ( tcnt > vindex ) + transTangents[vindex] += trans.rotation * tangents[vindex] * weight.second; + if ( bcnt > vindex ) + transBitangents[vindex] += trans.rotation * bitangents[vindex] * weight.second; } } } @@ -791,16 +798,19 @@ void Mesh::transformShapes() else x++; - Matrix natrix = trans.rotation; for ( const VertexWeight& vw : bw.weights ) { int vindex = vw.vertex; if ( vindex < 0 || vindex >= vcnt ) break; - transVerts[vindex] += trans * verts[vindex] * vw.weight; - transNorms[vindex] += natrix * norms[vindex] * vw.weight; - transTangents[vindex] += natrix * tangents[vindex] * vw.weight; - transBitangents[vindex] += natrix * bitangents[vindex] * vw.weight; + if ( vcnt > vindex ) + transVerts[vindex] += trans * verts[vindex] * vw.weight; + if ( ncnt > vindex ) + transNorms[vindex] += trans.rotation * norms[vindex] * vw.weight; + if ( tcnt > vindex ) + transTangents[vindex] += trans.rotation * tangents[vindex] * vw.weight; + if ( bcnt > vindex ) + transBitangents[vindex] += trans.rotation * bitangents[vindex] * vw.weight; } } } From 718a7ba958bd2cb7cebb81b4c1b45fb457bb9854 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sat, 9 Dec 2017 02:00:06 -0500 Subject: [PATCH 121/152] [Build] Cross-platform fixes (Hopefully) --- src/gl/gltexloaders.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/gl/gltexloaders.cpp b/src/gl/gltexloaders.cpp index e44b5b344..a4b3ec17b 100644 --- a/src/gl/gltexloaders.cpp +++ b/src/gl/gltexloaders.cpp @@ -48,6 +48,11 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#ifdef __APPLE__ +#include +#include +#endif + /*! @file gltexloaders.cpp * @brief Texture loading functions. @@ -72,13 +77,15 @@ bool extInitialized = false; bool extSupported = true; bool extStorageSupported = true; + +#ifndef __APPLE__ // OpenGL 4.2 PFNGLTEXSTORAGE2DPROC glTexStorage2D = nullptr; #ifdef _WIN32 PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC glCompressedTexSubImage2D = nullptr; -// Fallback PFNGLCOMPRESSEDTEXIMAGE2DPROC glCompressedTexImage2D = nullptr; #endif +#endif #define FOURCC_DXT1 MAKEFOURCC( 'D', 'X', 'T', '1' ) #define FOURCC_DXT3 MAKEFOURCC( 'D', 'X', 'T', '3' ) @@ -620,9 +627,11 @@ GLuint texLoadBMP( QIODevice & f, QString & texformat, GLenum & target, GLuint & GLuint texLoadDDS( const QString & filepath, QString & format, GLenum & target, GLuint & width, GLuint & height, GLuint & mipmaps, QByteArray & data, GLuint & id ) { if ( !extInitialized ) { +#ifndef __APPLE__ glTexStorage2D = (PFNGLTEXSTORAGE2DPROC)SOIL_GL_GetProcAddress( "glTexStorage2D" ); #ifdef _WIN32 glCompressedTexSubImage2D = (PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC)SOIL_GL_GetProcAddress( "glCompressedTexSubImage2D" ); +#endif #endif if ( !glTexStorage2D || !glCompressedTexSubImage2D ) extStorageSupported = false; From be938471f56f421c8b4a543128421b1c47e5f0a2 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Mon, 11 Dec 2017 06:24:57 -0500 Subject: [PATCH 122/152] [KFM] Fix crash with no kfm.xml from d940f0a --- src/model/kfmmodel.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/model/kfmmodel.cpp b/src/model/kfmmodel.cpp index 9029375cc..f4bf1f6c5 100644 --- a/src/model/kfmmodel.cpp +++ b/src/model/kfmmodel.cpp @@ -124,8 +124,9 @@ void KfmModel::clear() rootData.setIsCompound( true ); rootData.setIsConditionless( true ); insertType( root, rootData ); - kfmroot = root->child( 0 ); - kfmroot->setCondition( true ); + kfmroot = (root->childCount()) ? root->child( 0 ) : nullptr; + if ( kfmroot ) + kfmroot->setCondition( true ); version = 0x0200000b; endResetModel(); @@ -214,8 +215,9 @@ void KfmModel::insertType( NifItem * parent, const NifData & data, int at ) updateArrayItem( array ); } else if ( data.isCompound() ) { - NifBlockPtr compound = compounds.value( data.type() ); + if ( !compound ) + return; NifItem * branch = insertBranch( parent, data, at ); branch->prepareInsert( compound->types.count() ); From 318ec62b91b898d6650ff14571aa252a7c08932a Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Mon, 11 Dec 2017 08:40:01 -0500 Subject: [PATCH 123/152] [UI] Fix 6e43e29 to work with Qt 5.10 Use of NifModel::tr outside of the class crashed newer versions of Qt. --- src/model/nifmodel.cpp | 9 ++++----- src/model/nifmodel.h | 8 ++++++++ src/nifskope_ui.cpp | 4 ++-- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/model/nifmodel.cpp b/src/model/nifmodel.cpp index 3e1166e42..44246eefc 100644 --- a/src/model/nifmodel.cpp +++ b/src/model/nifmodel.cpp @@ -44,9 +44,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include -//! @file nifmodel.cpp The NIF data model. -const QString loadFail = NifModel::tr( "The NIF file could not be read. See Details for more information." ); +//! @file nifmodel.cpp The NIF data model. NifModel::NifModel( QObject * parent ) : BaseModel( parent ) { @@ -520,7 +519,7 @@ bool NifModel::updateArrayItem( NifItem * array ) auto m = tr( "[%1] Array %2 much too large. %3 bytes requested" ).arg( getBlockNumber( array ) ) .arg( array->name() ).arg( rows ); if ( msgMode == UserMessage ) { - Message::append( nullptr, loadFail, m, QMessageBox::Critical ); + Message::append( nullptr, tr( readFail ), m, QMessageBox::Critical ); } else { testMsg( m ); } @@ -529,7 +528,7 @@ bool NifModel::updateArrayItem( NifItem * array ) } else if ( rows < 0 ) { auto m = tr( "[%1] Array %2 invalid" ).arg( getBlockNumber( array ) ).arg( array->name() ); if ( msgMode == UserMessage ) { - Message::append( nullptr, loadFail, m, QMessageBox::Critical ); + Message::append( nullptr, tr( readFail ), m, QMessageBox::Critical ); } else { testMsg( m ); } @@ -1996,7 +1995,7 @@ bool NifModel::load( QIODevice & device ) catch ( QString & err ) { if ( msgMode == UserMessage ) { - Message::append( nullptr, loadFail, err, QMessageBox::Critical ); + Message::append( nullptr, tr( readFail ), err, QMessageBox::Critical ); } else { testMsg( err ); } diff --git a/src/model/nifmodel.h b/src/model/nifmodel.h index d65861dc1..71bd95ded 100644 --- a/src/model/nifmodel.h +++ b/src/model/nifmodel.h @@ -51,6 +51,14 @@ using SpellBookPtr = std::shared_ptr; //! @file nifmodel.h NifModel, NifModelEval + +//! Primary string for read failure +const char * const readFail = QT_TR_NOOP( "The NIF file could not be read. See Details for more information." ); + +//! Secondary string for read failure +const char * const readFailFinal = QT_TR_NOOP( "Failed to load %1" ); + + //! The main data model for the NIF file. class NifModel final : public BaseModel { diff --git a/src/nifskope_ui.cpp b/src/nifskope_ui.cpp index 5ddade129..05b4ae720 100644 --- a/src/nifskope_ui.cpp +++ b/src/nifskope_ui.cpp @@ -798,8 +798,8 @@ void NifSkope::onLoadComplete( bool success, QString & fname ) } else { // File failed to load - Message::append( this, tr( "The NIF file could not be read. See Details for more information." ), - tr( "Failed to load %1" ).arg( fname ), QMessageBox::Critical ); + Message::append( this, NifModel::tr( readFail ), + NifModel::tr( readFailFinal ).arg( fname ), QMessageBox::Critical ); nif->clear(); kfm->clear(); From 38109b1c0aa50eeb4076f8af6a2fb21ec82ed327 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Mon, 11 Dec 2017 08:45:56 -0500 Subject: [PATCH 124/152] [GL] Remove SOIL2 Using SOIL2 as a fallback for cubemap loading was removed when testing revealed that the SOIL2 loading was what broke cubemapping shaders for some people as it started working after switching to GLI. So SOIL2 no longer has any real use. --- NifSkope.pro | 29 +- lib/SOIL2/SOIL2.c | 3066 --------------- lib/SOIL2/SOIL2.h | 511 --- lib/SOIL2/etc1_utils.c | 680 ---- lib/SOIL2/etc1_utils.h | 106 - lib/SOIL2/image_DXT.c | 632 --- lib/SOIL2/image_DXT.h | 123 - lib/SOIL2/image_helper.c | 435 --- lib/SOIL2/image_helper.h | 115 - lib/SOIL2/jo_jpeg.h | 340 -- lib/SOIL2/pkm_helper.h | 19 - lib/SOIL2/pvr_helper.h | 264 -- lib/SOIL2/stb_image.h | 7240 ----------------------------------- lib/SOIL2/stb_image_write.h | 1092 ------ lib/SOIL2/stbi_DDS.h | 34 - lib/SOIL2/stbi_DDS_c.h | 589 --- lib/SOIL2/stbi_ext.h | 28 - lib/SOIL2/stbi_ext_c.h | 74 - lib/SOIL2/stbi_pkm.h | 34 - lib/SOIL2/stbi_pkm_c.h | 227 -- lib/SOIL2/stbi_pvr.h | 34 - lib/SOIL2/stbi_pvr_c.h | 1001 ----- src/gl/gltex.cpp | 9 +- src/gl/gltexloaders.cpp | 44 +- src/gl/gltexloaders.h | 6 + 25 files changed, 33 insertions(+), 16699 deletions(-) delete mode 100644 lib/SOIL2/SOIL2.c delete mode 100644 lib/SOIL2/SOIL2.h delete mode 100644 lib/SOIL2/etc1_utils.c delete mode 100644 lib/SOIL2/etc1_utils.h delete mode 100644 lib/SOIL2/image_DXT.c delete mode 100644 lib/SOIL2/image_DXT.h delete mode 100644 lib/SOIL2/image_helper.c delete mode 100644 lib/SOIL2/image_helper.h delete mode 100644 lib/SOIL2/jo_jpeg.h delete mode 100644 lib/SOIL2/pkm_helper.h delete mode 100644 lib/SOIL2/pvr_helper.h delete mode 100644 lib/SOIL2/stb_image.h delete mode 100644 lib/SOIL2/stb_image_write.h delete mode 100644 lib/SOIL2/stbi_DDS.h delete mode 100644 lib/SOIL2/stbi_DDS_c.h delete mode 100644 lib/SOIL2/stbi_ext.h delete mode 100644 lib/SOIL2/stbi_ext_c.h delete mode 100644 lib/SOIL2/stbi_pkm.h delete mode 100644 lib/SOIL2/stbi_pkm_c.h delete mode 100644 lib/SOIL2/stbi_pvr.h delete mode 100644 lib/SOIL2/stbi_pvr_c.h diff --git a/NifSkope.pro b/NifSkope.pro index e12e0c798..af9b8bc0a 100644 --- a/NifSkope.pro +++ b/NifSkope.pro @@ -17,7 +17,7 @@ contains(QT_VERSION, ^5\\.[0-6]\\..*) { CONFIG += c++14 # Dependencies -CONFIG += nvtristrip qhull soil2 zlib lz4 fsengine gli +CONFIG += nvtristrip qhull zlib lz4 fsengine gli # Debug/Release options CONFIG(debug, debug|release) { @@ -345,33 +345,6 @@ qhull { lib/qhull/src/libqhull/user.h } -soil2 { - INCLUDEPATH += lib/SOIL2 - HEADERS += \ - lib/SOIL2/etc1_utils.h \ - lib/SOIL2/image_DXT.h \ - lib/SOIL2/image_helper.h \ - lib/SOIL2/jo_jpeg.h \ - lib/SOIL2/pkm_helper.h \ - lib/SOIL2/pvr_helper.h \ - lib/SOIL2/SOIL2.h \ - lib/SOIL2/stb_image.h \ - lib/SOIL2/stb_image_write.h \ - lib/SOIL2/stbi_DDS.h \ - lib/SOIL2/stbi_DDS_c.h \ - lib/SOIL2/stbi_ext.h \ - lib/SOIL2/stbi_ext_c.h \ - lib/SOIL2/stbi_pkm.h \ - lib/SOIL2/stbi_pkm_c.h \ - lib/SOIL2/stbi_pvr.h \ - lib/SOIL2/stbi_pvr_c.h - SOURCES += \ - lib/SOIL2/etc1_utils.c \ - lib/SOIL2/image_DXT.c \ - lib/SOIL2/image_helper.c \ - lib/SOIL2/SOIL2.c -} - gli { INCLUDEPATH += lib/gli/gli lib/gli/external HEADERS += $$files($$PWD/lib/gli/gli/*.hpp, true) diff --git a/lib/SOIL2/SOIL2.c b/lib/SOIL2/SOIL2.c deleted file mode 100644 index ee14a1057..000000000 --- a/lib/SOIL2/SOIL2.c +++ /dev/null @@ -1,3066 +0,0 @@ -/* - Fork by Martin Lucas Golini - - Original author - Jonathan Dummer - 2007-07-26-10.36 - - Simple OpenGL Image Library 2 - - Public Domain - using Sean Barret's stb_image as a base - - Thanks to: - * Sean Barret - for the awesome stb_image - * Dan Venkitachalam - for finding some non-compliant DDS files, and patching some explicit casts - * everybody at gamedev.net -*/ - -#define SOIL_CHECK_FOR_GL_ERRORS 0 - -#if defined( __APPLE_CC__ ) || defined ( __APPLE__ ) - #include - - #if defined( __IPHONE__ ) || ( defined( TARGET_OS_IPHONE ) && TARGET_OS_IPHONE ) || ( defined( TARGET_IPHONE_SIMULATOR ) && TARGET_IPHONE_SIMULATOR ) - #define SOIL_PLATFORM_IOS - #include - #else - #define SOIL_PLATFORM_OSX - #endif -#elif defined( __ANDROID__ ) || defined( ANDROID ) - #define SOIL_PLATFORM_ANDROID -#elif ( defined ( linux ) || defined( __linux__ ) || defined( __FreeBSD__ ) || defined(__OpenBSD__) || defined( __NetBSD__ ) || defined( __DragonFly__ ) || defined( __SVR4 ) ) - #define SOIL_X11_PLATFORM -#endif - -#if ( defined( SOIL_PLATFORM_IOS ) || defined( SOIL_PLATFORM_ANDROID ) ) && ( !defined( SOIL_GLES1 ) && !defined( SOIL_GLES2 ) ) - #define SOIL_GLES2 -#endif - -#if ( defined( SOIL_GLES2 ) || defined( SOIL_GLES1 ) ) && !defined( SOIL_NO_EGL ) && !defined( SOIL_PLATFORM_IOS ) - #include -#endif - -#if defined( SOIL_GLES2 ) - #ifdef SOIL_PLATFORM_IOS - #include - #include - #else - #include - #include - #endif - - #define APIENTRY GL_APIENTRY -#elif defined( SOIL_GLES1 ) - #ifndef GL_GLEXT_PROTOTYPES - #define GL_GLEXT_PROTOTYPES - #endif - #ifdef SOIL_PLATFORM_IOS - #include - #include - #else - #include - #include - #endif - - #define APIENTRY GL_APIENTRY -#else - -#if defined( __WIN32__ ) || defined( _WIN32 ) || defined( WIN32 ) - #define SOIL_PLATFORM_WIN32 - #define WIN32_LEAN_AND_MEAN - #include - #include - #include - - #ifndef GL_UNSIGNED_SHORT_4_4_4_4 - #define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 - #endif - #ifndef GL_UNSIGNED_SHORT_5_5_5_1 - #define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 - #endif - #ifndef GL_UNSIGNED_SHORT_5_6_5 - #define GL_UNSIGNED_SHORT_5_6_5 0x8363 - #endif -#elif defined(__APPLE__) || defined(__APPLE_CC__) - /* I can't test this Apple stuff! */ - #include - #include - #define APIENTRY -#elif defined( SOIL_X11_PLATFORM ) - #include - #include -#else - #include -#endif - -#endif - -#ifndef GL_BGRA -#define GL_BGRA 0x80E1 -#endif - -#ifndef GL_RG -#define GL_RG 0x8227 -#endif - -#include "SOIL2.h" -#define STB_IMAGE_IMPLEMENTATION -#include "stb_image.h" -#define STB_IMAGE_WRITE_IMPLEMENTATION -#include "stb_image_write.h" -#include "image_helper.h" -#include "image_DXT.h" -#include "pvr_helper.h" -#include "pkm_helper.h" -#include "jo_jpeg.h" - -#include -#include - -/* error reporting */ -const char *result_string_pointer = "SOIL initialized"; - -/* for loading cube maps */ -enum{ - SOIL_CAPABILITY_UNKNOWN = -1, - SOIL_CAPABILITY_NONE = 0, - SOIL_CAPABILITY_PRESENT = 1 -}; -static int has_cubemap_capability = SOIL_CAPABILITY_UNKNOWN; -int query_cubemap_capability( void ); -#define SOIL_TEXTURE_WRAP_R 0x8072 -#define SOIL_CLAMP_TO_EDGE 0x812F -#define SOIL_NORMAL_MAP 0x8511 -#define SOIL_REFLECTION_MAP 0x8512 -#define SOIL_TEXTURE_CUBE_MAP 0x8513 -#define SOIL_TEXTURE_BINDING_CUBE_MAP 0x8514 -#define SOIL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 -#define SOIL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516 -#define SOIL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517 -#define SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518 -#define SOIL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519 -#define SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A -#define SOIL_PROXY_TEXTURE_CUBE_MAP 0x851B -#define SOIL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C -/* for non-power-of-two texture */ -#define SOIL_IS_POW2( v ) ( ( v & ( v - 1 ) ) == 0 ) -static int has_NPOT_capability = SOIL_CAPABILITY_UNKNOWN; -int query_NPOT_capability( void ); -/* for texture rectangles */ -static int has_tex_rectangle_capability = SOIL_CAPABILITY_UNKNOWN; -int query_tex_rectangle_capability( void ); -#define SOIL_TEXTURE_RECTANGLE_ARB 0x84F5 -#define SOIL_MAX_RECTANGLE_TEXTURE_SIZE_ARB 0x84F8 -/* for using DXT compression */ -static int has_DXT_capability = SOIL_CAPABILITY_UNKNOWN; -int query_DXT_capability( void ); -#define SOIL_GL_SRGB 0x8C40 -#define SOIL_GL_SRGB_ALPHA 0x8C42 -#define SOIL_RGB_S3TC_DXT1 0x83F0 -#define SOIL_RGBA_S3TC_DXT1 0x83F1 -#define SOIL_RGBA_S3TC_DXT3 0x83F2 -#define SOIL_RGBA_S3TC_DXT5 0x83F3 -#define SOIL_GL_COMPRESSED_SRGB_S3TC_DXT1_EXT 0x8C4C -#define SOIL_GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT 0x8C4F -static int has_sRGB_capability = SOIL_CAPABILITY_UNKNOWN; -int query_sRGB_capability( void ); -typedef void (APIENTRY * P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid * data); -static P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC soilGlCompressedTexImage2D = NULL; - -typedef void (APIENTRY *P_SOIL_GLGENERATEMIPMAPPROC)(GLenum target); -static P_SOIL_GLGENERATEMIPMAPPROC soilGlGenerateMipmap = NULL; - -static int has_gen_mipmap_capability = SOIL_CAPABILITY_UNKNOWN; -static int query_gen_mipmap_capability( void ); - -static int has_PVR_capability = SOIL_CAPABILITY_UNKNOWN; -int query_PVR_capability( void ); -static int has_BGRA8888_capability = SOIL_CAPABILITY_UNKNOWN; -int query_BGRA8888_capability( void ); -static int has_ETC1_capability = SOIL_CAPABILITY_UNKNOWN; -int query_ETC1_capability( void ); - -/* GL_IMG_texture_compression_pvrtc */ -#define SOIL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00 -#define SOIL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG 0x8C01 -#define SOIL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02 -#define SOIL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG 0x8C03 -#define SOIL_GL_ETC1_RGB8_OES 0x8D64 - -#if defined( SOIL_X11_PLATFORM ) || defined( SOIL_PLATFORM_WIN32 ) || defined( SOIL_PLATFORM_OSX ) -typedef const GLubyte *(APIENTRY * P_SOIL_glGetStringiFunc) (GLenum, GLuint); -static P_SOIL_glGetStringiFunc soilGlGetStringiFunc = NULL; - -static int isAtLeastGL3() -{ - static int is_gl3 = SOIL_CAPABILITY_UNKNOWN; - - if ( SOIL_CAPABILITY_UNKNOWN == is_gl3 ) - { - const char * verstr = (const char *) glGetString( GL_VERSION ); - is_gl3 = ( verstr && ( atoi(verstr) >= 3 ) ); - } - - return is_gl3; -} -#endif - -#ifdef SOIL_PLATFORM_WIN32 -static int soilTestWinProcPointer(const PROC pTest) -{ - ptrdiff_t iTest; - if(!pTest) return 0; - iTest = (ptrdiff_t)pTest; - if(iTest == 1 || iTest == 2 || iTest == 3 || iTest == -1) return 0; - return 1; -} -#endif - -void * SOIL_GL_GetProcAddress(const char *proc) -{ - void *func = NULL; - -#if defined( SOIL_PLATFORM_IOS ) - func = dlsym( RTLD_DEFAULT, proc ); -#elif defined( SOIL_GLES2 ) || defined( SOIL_GLES1 ) - #ifndef SOIL_NO_EGL - func = eglGetProcAddress( proc ); - #else - func = NULL; - #endif -#elif defined( SOIL_PLATFORM_WIN32 ) - func = wglGetProcAddress( proc ); - - if (!soilTestWinProcPointer((const PROC)func)) - func = NULL; -#elif defined( SOIL_PLATFORM_OSX ) - /* I can't test this Apple stuff! */ - CFBundleRef bundle; - CFURLRef bundleURL = - CFURLCreateWithFileSystemPath( - kCFAllocatorDefault, - CFSTR("/System/Library/Frameworks/OpenGL.framework"), - kCFURLPOSIXPathStyle, - true ); - CFStringRef extensionName = - CFStringCreateWithCString( - kCFAllocatorDefault, - proc, - kCFStringEncodingASCII ); - bundle = CFBundleCreate( kCFAllocatorDefault, bundleURL ); - assert( bundle != NULL ); - - func = CFBundleGetFunctionPointerForName( bundle, extensionName ); - - CFRelease( bundleURL ); - CFRelease( extensionName ); - CFRelease( bundle ); -#elif defined( SOIL_X11_PLATFORM ) - func = -#if !defined(GLX_VERSION_1_4) - glXGetProcAddressARB -#else - glXGetProcAddress -#endif - ( (const GLubyte *)proc ); -#endif - - return func; -} - -/* Based on the SDL2 implementation */ -int SOIL_GL_ExtensionSupported(const char *extension) -{ - const char *extensions; - const char *start; - const char *where, *terminator; - - /* Extension names should not have spaces. */ - where = strchr(extension, ' '); - - if (where || *extension == '\0') - { - return 0; - } - - #if defined( SOIL_X11_PLATFORM ) || defined( SOIL_PLATFORM_WIN32 ) || defined( SOIL_PLATFORM_OSX ) - /* Lookup the available extensions */ - if ( isAtLeastGL3() ) - { - GLint num_exts = 0; - GLint i; - - if ( NULL == soilGlGetStringiFunc ) - { - soilGlGetStringiFunc = (P_SOIL_glGetStringiFunc)SOIL_GL_GetProcAddress("glGetStringi"); - - if ( NULL == soilGlGetStringiFunc ) - { - return 0; - } - } - - #ifndef GL_NUM_EXTENSIONS - #define GL_NUM_EXTENSIONS 0x821D - #endif - glGetIntegerv(GL_NUM_EXTENSIONS, &num_exts); - for (i = 0; i < num_exts; i++) - { - const char *thisext = (const char *) soilGlGetStringiFunc(GL_EXTENSIONS, i); - - if (strcmp(thisext, extension) == 0) - { - return 1; - } - } - - return 0; - } - #endif - - /* Try the old way with glGetString(GL_EXTENSIONS) ... */ - extensions = (const char *) glGetString(GL_EXTENSIONS); - - if (!extensions) - { - return 0; - } - - /* - * It takes a bit of care to be fool-proof about parsing the OpenGL - * extensions string. Don't be fooled by sub-strings, etc. - */ - start = extensions; - - for (;;) { - where = strstr(start, extension); - - if (!where) - break; - - terminator = where + strlen(extension); - - if (where == start || *(where - 1) == ' ') - if (*terminator == ' ' || *terminator == '\0') - return 1; - - start = terminator; - } - - return 0; -} - -/* other functions */ -unsigned int - SOIL_internal_create_OGL_texture - ( - const unsigned char *const data, - int *width, int *height, int channels, - unsigned int reuse_texture_ID, - unsigned int flags, - unsigned int opengl_texture_type, - unsigned int opengl_texture_target, - unsigned int texture_check_size_enum - ); - -/* and the code magic begins here [8^) */ -unsigned int - SOIL_load_OGL_texture - ( - const char *filename, - int force_channels, - unsigned int reuse_texture_ID, - unsigned int flags - ) -{ - /* variables */ - unsigned char* img; - int width, height, channels; - unsigned int tex_id; - /* does the user want direct uploading of the image as a DDS file? */ - if( flags & SOIL_FLAG_DDS_LOAD_DIRECT ) - { - /* 1st try direct loading of the image as a DDS file - note: direct uploading will only load what is in the - DDS file, no MIPmaps will be generated, the image will - not be flipped, etc. */ - tex_id = SOIL_direct_load_DDS( filename, reuse_texture_ID, flags, 0 ); - if( tex_id ) - { - /* hey, it worked!! */ - return tex_id; - } - } - - if( flags & SOIL_FLAG_PVR_LOAD_DIRECT ) - { - tex_id = SOIL_direct_load_PVR( filename, reuse_texture_ID, flags, 0 ); - if( tex_id ) - { - /* hey, it worked!! */ - return tex_id; - } - } - - if( flags & SOIL_FLAG_ETC1_LOAD_DIRECT ) - { - tex_id = SOIL_direct_load_ETC1( filename, reuse_texture_ID, flags ); - if( tex_id ) - { - /* hey, it worked!! */ - return tex_id; - } - } - - /* try to load the image */ - img = SOIL_load_image( filename, &width, &height, &channels, force_channels ); - /* channels holds the original number of channels, which may have been forced */ - if( (force_channels >= 1) && (force_channels <= 4) ) - { - channels = force_channels; - } - if( NULL == img ) - { - /* image loading failed */ - result_string_pointer = stbi_failure_reason(); - return 0; - } - /* OK, make it a texture! */ - tex_id = SOIL_internal_create_OGL_texture( - img, &width, &height, channels, - reuse_texture_ID, flags, - GL_TEXTURE_2D, GL_TEXTURE_2D, - GL_MAX_TEXTURE_SIZE ); - /* and nuke the image data */ - SOIL_free_image_data( img ); - /* and return the handle, such as it is */ - return tex_id; -} - -unsigned int - SOIL_load_OGL_HDR_texture - ( - const char *filename, - int fake_HDR_format, - int rescale_to_max, - unsigned int reuse_texture_ID, - unsigned int flags - ) -{ - /* variables */ - unsigned char* img = NULL; - int width, height, channels; - unsigned int tex_id; - /* no direct uploading of the image as a DDS file */ - /* error check */ - if( (fake_HDR_format != SOIL_HDR_RGBE) && - (fake_HDR_format != SOIL_HDR_RGBdivA) && - (fake_HDR_format != SOIL_HDR_RGBdivA2) ) - { - result_string_pointer = "Invalid fake HDR format specified"; - return 0; - } - - /* check if the image is HDR */ - if ( stbi_is_hdr( filename ) ) - { - /* try to load the image (only the HDR type) */ - img = stbi_load( filename, &width, &height, &channels, 4 ); - } - - /* channels holds the original number of channels, which may have been forced */ - if( NULL == img ) - { - /* image loading failed */ - result_string_pointer = stbi_failure_reason(); - return 0; - } - /* the load worked, do I need to convert it? */ - if( fake_HDR_format == SOIL_HDR_RGBdivA ) - { - RGBE_to_RGBdivA( img, width, height, rescale_to_max ); - } else if( fake_HDR_format == SOIL_HDR_RGBdivA2 ) - { - RGBE_to_RGBdivA2( img, width, height, rescale_to_max ); - } - /* OK, make it a texture! */ - tex_id = SOIL_internal_create_OGL_texture( - img, &width, &height, channels, - reuse_texture_ID, flags, - GL_TEXTURE_2D, GL_TEXTURE_2D, - GL_MAX_TEXTURE_SIZE ); - /* and nuke the image data */ - SOIL_free_image_data( img ); - /* and return the handle, such as it is */ - return tex_id; -} - -unsigned int - SOIL_load_OGL_texture_from_memory - ( - const unsigned char *const buffer, - int buffer_length, - int force_channels, - unsigned int reuse_texture_ID, - unsigned int flags - ) -{ - /* variables */ - unsigned char* img; - int width, height, channels; - unsigned int tex_id; - /* does the user want direct uploading of the image as a DDS file? */ - if( flags & SOIL_FLAG_DDS_LOAD_DIRECT ) - { - /* 1st try direct loading of the image as a DDS file - note: direct uploading will only load what is in the - DDS file, no MIPmaps will be generated, the image will - not be flipped, etc. */ - tex_id = SOIL_direct_load_DDS_from_memory( - buffer, buffer_length, - reuse_texture_ID, flags, 0 ); - if( tex_id ) - { - /* hey, it worked!! */ - return tex_id; - } - } - - if( flags & SOIL_FLAG_PVR_LOAD_DIRECT ) - { - tex_id = SOIL_direct_load_PVR_from_memory( - buffer, buffer_length, - reuse_texture_ID, flags, 0 ); - if( tex_id ) - { - /* hey, it worked!! */ - return tex_id; - } - } - - if( flags & SOIL_FLAG_ETC1_LOAD_DIRECT ) - { - tex_id = SOIL_direct_load_ETC1_from_memory( - buffer, buffer_length, - reuse_texture_ID, flags ); - if( tex_id ) - { - /* hey, it worked!! */ - return tex_id; - } - } - - /* try to load the image */ - img = SOIL_load_image_from_memory( - buffer, buffer_length, - &width, &height, &channels, - force_channels ); - /* channels holds the original number of channels, which may have been forced */ - if( (force_channels >= 1) && (force_channels <= 4) ) - { - channels = force_channels; - } - if( NULL == img ) - { - /* image loading failed */ - result_string_pointer = stbi_failure_reason(); - return 0; - } - /* OK, make it a texture! */ - tex_id = SOIL_internal_create_OGL_texture( - img, &width, &height, channels, - reuse_texture_ID, flags, - GL_TEXTURE_2D, GL_TEXTURE_2D, - GL_MAX_TEXTURE_SIZE ); - /* and nuke the image data */ - SOIL_free_image_data( img ); - /* and return the handle, such as it is */ - return tex_id; -} - -unsigned int - SOIL_load_OGL_cubemap - ( - const char *x_pos_file, - const char *x_neg_file, - const char *y_pos_file, - const char *y_neg_file, - const char *z_pos_file, - const char *z_neg_file, - int force_channels, - unsigned int reuse_texture_ID, - unsigned int flags - ) -{ - /* variables */ - unsigned char* img; - int width, height, channels; - unsigned int tex_id; - /* error checking */ - if( (x_pos_file == NULL) || - (x_neg_file == NULL) || - (y_pos_file == NULL) || - (y_neg_file == NULL) || - (z_pos_file == NULL) || - (z_neg_file == NULL) ) - { - result_string_pointer = "Invalid cube map files list"; - return 0; - } - /* capability checking */ - if( query_cubemap_capability() != SOIL_CAPABILITY_PRESENT ) - { - result_string_pointer = "No cube map capability present"; - return 0; - } - /* 1st face: try to load the image */ - img = SOIL_load_image( x_pos_file, &width, &height, &channels, force_channels ); - /* channels holds the original number of channels, which may have been forced */ - if( (force_channels >= 1) && (force_channels <= 4) ) - { - channels = force_channels; - } - if( NULL == img ) - { - /* image loading failed */ - result_string_pointer = stbi_failure_reason(); - return 0; - } - /* upload the texture, and create a texture ID if necessary */ - tex_id = SOIL_internal_create_OGL_texture( - img, &width, &height, channels, - reuse_texture_ID, flags, - SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_POSITIVE_X, - SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); - /* and nuke the image data */ - SOIL_free_image_data( img ); - /* continue? */ - if( tex_id != 0 ) - { - /* 1st face: try to load the image */ - img = SOIL_load_image( x_neg_file, &width, &height, &channels, force_channels ); - /* channels holds the original number of channels, which may have been forced */ - if( (force_channels >= 1) && (force_channels <= 4) ) - { - channels = force_channels; - } - if( NULL == img ) - { - /* image loading failed */ - result_string_pointer = stbi_failure_reason(); - return 0; - } - /* upload the texture, but reuse the assigned texture ID */ - tex_id = SOIL_internal_create_OGL_texture( - img, &width, &height, channels, - tex_id, flags, - SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_NEGATIVE_X, - SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); - /* and nuke the image data */ - SOIL_free_image_data( img ); - } - /* continue? */ - if( tex_id != 0 ) - { - /* 1st face: try to load the image */ - img = SOIL_load_image( y_pos_file, &width, &height, &channels, force_channels ); - /* channels holds the original number of channels, which may have been forced */ - if( (force_channels >= 1) && (force_channels <= 4) ) - { - channels = force_channels; - } - if( NULL == img ) - { - /* image loading failed */ - result_string_pointer = stbi_failure_reason(); - return 0; - } - /* upload the texture, but reuse the assigned texture ID */ - tex_id = SOIL_internal_create_OGL_texture( - img, &width, &height, channels, - tex_id, flags, - SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_POSITIVE_Y, - SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); - /* and nuke the image data */ - SOIL_free_image_data( img ); - } - /* continue? */ - if( tex_id != 0 ) - { - /* 1st face: try to load the image */ - img = SOIL_load_image( y_neg_file, &width, &height, &channels, force_channels ); - /* channels holds the original number of channels, which may have been forced */ - if( (force_channels >= 1) && (force_channels <= 4) ) - { - channels = force_channels; - } - if( NULL == img ) - { - /* image loading failed */ - result_string_pointer = stbi_failure_reason(); - return 0; - } - /* upload the texture, but reuse the assigned texture ID */ - tex_id = SOIL_internal_create_OGL_texture( - img, &width, &height, channels, - tex_id, flags, - SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Y, - SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); - /* and nuke the image data */ - SOIL_free_image_data( img ); - } - /* continue? */ - if( tex_id != 0 ) - { - /* 1st face: try to load the image */ - img = SOIL_load_image( z_pos_file, &width, &height, &channels, force_channels ); - /* channels holds the original number of channels, which may have been forced */ - if( (force_channels >= 1) && (force_channels <= 4) ) - { - channels = force_channels; - } - if( NULL == img ) - { - /* image loading failed */ - result_string_pointer = stbi_failure_reason(); - return 0; - } - /* upload the texture, but reuse the assigned texture ID */ - tex_id = SOIL_internal_create_OGL_texture( - img, &width, &height, channels, - tex_id, flags, - SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_POSITIVE_Z, - SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); - /* and nuke the image data */ - SOIL_free_image_data( img ); - } - /* continue? */ - if( tex_id != 0 ) - { - /* 1st face: try to load the image */ - img = SOIL_load_image( z_neg_file, &width, &height, &channels, force_channels ); - /* channels holds the original number of channels, which may have been forced */ - if( (force_channels >= 1) && (force_channels <= 4) ) - { - channels = force_channels; - } - if( NULL == img ) - { - /* image loading failed */ - result_string_pointer = stbi_failure_reason(); - return 0; - } - /* upload the texture, but reuse the assigned texture ID */ - tex_id = SOIL_internal_create_OGL_texture( - img, &width, &height, channels, - tex_id, flags, - SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Z, - SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); - /* and nuke the image data */ - SOIL_free_image_data( img ); - } - /* and return the handle, such as it is */ - return tex_id; -} - -unsigned int - SOIL_load_OGL_cubemap_from_memory - ( - const unsigned char *const x_pos_buffer, - int x_pos_buffer_length, - const unsigned char *const x_neg_buffer, - int x_neg_buffer_length, - const unsigned char *const y_pos_buffer, - int y_pos_buffer_length, - const unsigned char *const y_neg_buffer, - int y_neg_buffer_length, - const unsigned char *const z_pos_buffer, - int z_pos_buffer_length, - const unsigned char *const z_neg_buffer, - int z_neg_buffer_length, - int force_channels, - unsigned int reuse_texture_ID, - unsigned int flags - ) -{ - /* variables */ - unsigned char* img; - int width, height, channels; - unsigned int tex_id; - /* error checking */ - if( (x_pos_buffer == NULL) || - (x_neg_buffer == NULL) || - (y_pos_buffer == NULL) || - (y_neg_buffer == NULL) || - (z_pos_buffer == NULL) || - (z_neg_buffer == NULL) ) - { - result_string_pointer = "Invalid cube map buffers list"; - return 0; - } - /* capability checking */ - if( query_cubemap_capability() != SOIL_CAPABILITY_PRESENT ) - { - result_string_pointer = "No cube map capability present"; - return 0; - } - /* 1st face: try to load the image */ - img = SOIL_load_image_from_memory( - x_pos_buffer, x_pos_buffer_length, - &width, &height, &channels, force_channels ); - /* channels holds the original number of channels, which may have been forced */ - if( (force_channels >= 1) && (force_channels <= 4) ) - { - channels = force_channels; - } - if( NULL == img ) - { - /* image loading failed */ - result_string_pointer = stbi_failure_reason(); - return 0; - } - /* upload the texture, and create a texture ID if necessary */ - tex_id = SOIL_internal_create_OGL_texture( - img, &width, &height, channels, - reuse_texture_ID, flags, - SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_POSITIVE_X, - SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); - /* and nuke the image data */ - SOIL_free_image_data( img ); - /* continue? */ - if( tex_id != 0 ) - { - /* 1st face: try to load the image */ - img = SOIL_load_image_from_memory( - x_neg_buffer, x_neg_buffer_length, - &width, &height, &channels, force_channels ); - /* channels holds the original number of channels, which may have been forced */ - if( (force_channels >= 1) && (force_channels <= 4) ) - { - channels = force_channels; - } - if( NULL == img ) - { - /* image loading failed */ - result_string_pointer = stbi_failure_reason(); - return 0; - } - /* upload the texture, but reuse the assigned texture ID */ - tex_id = SOIL_internal_create_OGL_texture( - img, &width, &height, channels, - tex_id, flags, - SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_NEGATIVE_X, - SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); - /* and nuke the image data */ - SOIL_free_image_data( img ); - } - /* continue? */ - if( tex_id != 0 ) - { - /* 1st face: try to load the image */ - img = SOIL_load_image_from_memory( - y_pos_buffer, y_pos_buffer_length, - &width, &height, &channels, force_channels ); - /* channels holds the original number of channels, which may have been forced */ - if( (force_channels >= 1) && (force_channels <= 4) ) - { - channels = force_channels; - } - if( NULL == img ) - { - /* image loading failed */ - result_string_pointer = stbi_failure_reason(); - return 0; - } - /* upload the texture, but reuse the assigned texture ID */ - tex_id = SOIL_internal_create_OGL_texture( - img, &width, &height, channels, - tex_id, flags, - SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_POSITIVE_Y, - SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); - /* and nuke the image data */ - SOIL_free_image_data( img ); - } - /* continue? */ - if( tex_id != 0 ) - { - /* 1st face: try to load the image */ - img = SOIL_load_image_from_memory( - y_neg_buffer, y_neg_buffer_length, - &width, &height, &channels, force_channels ); - /* channels holds the original number of channels, which may have been forced */ - if( (force_channels >= 1) && (force_channels <= 4) ) - { - channels = force_channels; - } - if( NULL == img ) - { - /* image loading failed */ - result_string_pointer = stbi_failure_reason(); - return 0; - } - /* upload the texture, but reuse the assigned texture ID */ - tex_id = SOIL_internal_create_OGL_texture( - img, &width, &height, channels, - tex_id, flags, - SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Y, - SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); - /* and nuke the image data */ - SOIL_free_image_data( img ); - } - /* continue? */ - if( tex_id != 0 ) - { - /* 1st face: try to load the image */ - img = SOIL_load_image_from_memory( - z_pos_buffer, z_pos_buffer_length, - &width, &height, &channels, force_channels ); - /* channels holds the original number of channels, which may have been forced */ - if( (force_channels >= 1) && (force_channels <= 4) ) - { - channels = force_channels; - } - if( NULL == img ) - { - /* image loading failed */ - result_string_pointer = stbi_failure_reason(); - return 0; - } - /* upload the texture, but reuse the assigned texture ID */ - tex_id = SOIL_internal_create_OGL_texture( - img, &width, &height, channels, - tex_id, flags, - SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_POSITIVE_Z, - SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); - /* and nuke the image data */ - SOIL_free_image_data( img ); - } - /* continue? */ - if( tex_id != 0 ) - { - /* 1st face: try to load the image */ - img = SOIL_load_image_from_memory( - z_neg_buffer, z_neg_buffer_length, - &width, &height, &channels, force_channels ); - /* channels holds the original number of channels, which may have been forced */ - if( (force_channels >= 1) && (force_channels <= 4) ) - { - channels = force_channels; - } - if( NULL == img ) - { - /* image loading failed */ - result_string_pointer = stbi_failure_reason(); - return 0; - } - /* upload the texture, but reuse the assigned texture ID */ - tex_id = SOIL_internal_create_OGL_texture( - img, &width, &height, channels, - tex_id, flags, - SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Z, - SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); - /* and nuke the image data */ - SOIL_free_image_data( img ); - } - /* and return the handle, such as it is */ - return tex_id; -} - -unsigned int - SOIL_load_OGL_single_cubemap - ( - const char *filename, - const char face_order[6], - int force_channels, - unsigned int reuse_texture_ID, - unsigned int flags - ) -{ - /* variables */ - unsigned char* img; - int width, height, channels, i; - unsigned int tex_id = 0; - /* error checking */ - if( filename == NULL ) - { - result_string_pointer = "Invalid single cube map file name"; - return 0; - } - /* does the user want direct uploading of the image as a DDS file? */ - if( flags & SOIL_FLAG_DDS_LOAD_DIRECT ) - { - /* 1st try direct loading of the image as a DDS file - note: direct uploading will only load what is in the - DDS file, no MIPmaps will be generated, the image will - not be flipped, etc. */ - tex_id = SOIL_direct_load_DDS( filename, reuse_texture_ID, flags, 1 ); - if( tex_id ) - { - /* hey, it worked!! */ - return tex_id; - } - } - - if ( flags & SOIL_FLAG_PVR_LOAD_DIRECT ) - { - tex_id = SOIL_direct_load_PVR( filename, reuse_texture_ID, flags, 1 ); - if( tex_id ) - { - /* hey, it worked!! */ - return tex_id; - } - } - - if ( flags & SOIL_FLAG_ETC1_LOAD_DIRECT ) - { - return 0; - } - - /* face order checking */ - for( i = 0; i < 6; ++i ) - { - if( (face_order[i] != 'N') && - (face_order[i] != 'S') && - (face_order[i] != 'W') && - (face_order[i] != 'E') && - (face_order[i] != 'U') && - (face_order[i] != 'D') ) - { - result_string_pointer = "Invalid single cube map face order"; - return 0; - }; - } - /* capability checking */ - if( query_cubemap_capability() != SOIL_CAPABILITY_PRESENT ) - { - result_string_pointer = "No cube map capability present"; - return 0; - } - /* 1st off, try to load the full image */ - img = SOIL_load_image( filename, &width, &height, &channels, force_channels ); - /* channels holds the original number of channels, which may have been forced */ - if( (force_channels >= 1) && (force_channels <= 4) ) - { - channels = force_channels; - } - if( NULL == img ) - { - /* image loading failed */ - result_string_pointer = stbi_failure_reason(); - return 0; - } - /* now, does this image have the right dimensions? */ - if( (width != 6*height) && - (6*width != height) ) - { - SOIL_free_image_data( img ); - result_string_pointer = "Single cubemap image must have a 6:1 ratio"; - return 0; - } - /* try the image split and create */ - tex_id = SOIL_create_OGL_single_cubemap( - img, width, height, channels, - face_order, reuse_texture_ID, flags - ); - /* nuke the temporary image data and return the texture handle */ - SOIL_free_image_data( img ); - return tex_id; -} - -unsigned int - SOIL_load_OGL_single_cubemap_from_memory - ( - const unsigned char *const buffer, - int buffer_length, - const char face_order[6], - int force_channels, - unsigned int reuse_texture_ID, - unsigned int flags - ) -{ - /* variables */ - unsigned char* img; - int width, height, channels, i; - unsigned int tex_id = 0; - /* error checking */ - if( buffer == NULL ) - { - result_string_pointer = "Invalid single cube map buffer"; - return 0; - } - /* does the user want direct uploading of the image as a DDS file? */ - if( flags & SOIL_FLAG_DDS_LOAD_DIRECT ) - { - /* 1st try direct loading of the image as a DDS file - note: direct uploading will only load what is in the - DDS file, no MIPmaps will be generated, the image will - not be flipped, etc. */ - tex_id = SOIL_direct_load_DDS_from_memory( - buffer, buffer_length, - reuse_texture_ID, flags, 1 ); - if( tex_id ) - { - /* hey, it worked!! */ - return tex_id; - } - } - - if ( flags & SOIL_FLAG_PVR_LOAD_DIRECT ) - { - tex_id = SOIL_direct_load_PVR_from_memory( - buffer, buffer_length, - reuse_texture_ID, flags, 1 ); - if ( tex_id ) - { - /* hey, it worked!! */ - return tex_id; - } - } - - if ( flags & SOIL_FLAG_ETC1_LOAD_DIRECT ) - { - return 0; - } - - /* face order checking */ - for( i = 0; i < 6; ++i ) - { - if( (face_order[i] != 'N') && - (face_order[i] != 'S') && - (face_order[i] != 'W') && - (face_order[i] != 'E') && - (face_order[i] != 'U') && - (face_order[i] != 'D') ) - { - result_string_pointer = "Invalid single cube map face order"; - return 0; - }; - } - /* capability checking */ - if( query_cubemap_capability() != SOIL_CAPABILITY_PRESENT ) - { - result_string_pointer = "No cube map capability present"; - return 0; - } - /* 1st off, try to load the full image */ - img = SOIL_load_image_from_memory( - buffer, buffer_length, - &width, &height, &channels, - force_channels ); - /* channels holds the original number of channels, which may have been forced */ - if( (force_channels >= 1) && (force_channels <= 4) ) - { - channels = force_channels; - } - if( NULL == img ) - { - /* image loading failed */ - result_string_pointer = stbi_failure_reason(); - return 0; - } - /* now, does this image have the right dimensions? */ - if( (width != 6*height) && - (6*width != height) ) - { - SOIL_free_image_data( img ); - result_string_pointer = "Single cubemap image must have a 6:1 ratio"; - return 0; - } - /* try the image split and create */ - tex_id = SOIL_create_OGL_single_cubemap( - img, width, height, channels, - face_order, reuse_texture_ID, flags - ); - /* nuke the temporary image data and return the texture handle */ - SOIL_free_image_data( img ); - return tex_id; -} - -unsigned int - SOIL_create_OGL_single_cubemap - ( - const unsigned char *const data, - int width, int height, int channels, - const char face_order[6], - unsigned int reuse_texture_ID, - unsigned int flags - ) -{ - /* variables */ - unsigned char* sub_img; - int dw, dh, sz, i; - unsigned int tex_id; - /* error checking */ - if( data == NULL ) - { - result_string_pointer = "Invalid single cube map image data"; - return 0; - } - /* face order checking */ - for( i = 0; i < 6; ++i ) - { - if( (face_order[i] != 'N') && - (face_order[i] != 'S') && - (face_order[i] != 'W') && - (face_order[i] != 'E') && - (face_order[i] != 'U') && - (face_order[i] != 'D') ) - { - result_string_pointer = "Invalid single cube map face order"; - return 0; - }; - } - /* capability checking */ - if( query_cubemap_capability() != SOIL_CAPABILITY_PRESENT ) - { - result_string_pointer = "No cube map capability present"; - return 0; - } - /* now, does this image have the right dimensions? */ - if( (width != 6*height) && - (6*width != height) ) - { - result_string_pointer = "Single cubemap image must have a 6:1 ratio"; - return 0; - } - /* which way am I stepping? */ - if( width > height ) - { - dw = height; - dh = 0; - } else - { - dw = 0; - dh = width; - } - sz = dw+dh; - sub_img = (unsigned char *)malloc( sz*sz*channels ); - /* do the splitting and uploading */ - tex_id = reuse_texture_ID; - for( i = 0; i < 6; ++i ) - { - int x, y, idx = 0; - unsigned int cubemap_target = 0; - /* copy in the sub-image */ - for( y = i*dh; y < i*dh+sz; ++y ) - { - for( x = i*dw*channels; x < (i*dw+sz)*channels; ++x ) - { - sub_img[idx++] = data[y*width*channels+x]; - } - } - /* what is my texture target? - remember, this coordinate system is - LHS if viewed from inside the cube! */ - switch( face_order[i] ) - { - case 'N': - cubemap_target = SOIL_TEXTURE_CUBE_MAP_POSITIVE_Z; - break; - case 'S': - cubemap_target = SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Z; - break; - case 'W': - cubemap_target = SOIL_TEXTURE_CUBE_MAP_NEGATIVE_X; - break; - case 'E': - cubemap_target = SOIL_TEXTURE_CUBE_MAP_POSITIVE_X; - break; - case 'U': - cubemap_target = SOIL_TEXTURE_CUBE_MAP_POSITIVE_Y; - break; - case 'D': - cubemap_target = SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Y; - break; - } - /* upload it as a texture */ - tex_id = SOIL_internal_create_OGL_texture( - sub_img, &sz, &sz, channels, - tex_id, flags, - SOIL_TEXTURE_CUBE_MAP, - cubemap_target, - SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); - } - /* and nuke the image and sub-image data */ - SOIL_free_image_data( sub_img ); - /* and return the handle, such as it is */ - return tex_id; -} - -unsigned int - SOIL_create_OGL_texture - ( - const unsigned char *const data, - int *width, int *height, int channels, - unsigned int reuse_texture_ID, - unsigned int flags - ) -{ - /* wrapper function for 2D textures */ - return SOIL_internal_create_OGL_texture( - data, width, height, channels, - reuse_texture_ID, flags, - GL_TEXTURE_2D, GL_TEXTURE_2D, - GL_MAX_TEXTURE_SIZE ); -} - -#if SOIL_CHECK_FOR_GL_ERRORS -void check_for_GL_errors( const char *calling_location ) -{ - /* check for errors */ - GLenum err_code = glGetError(); - while( GL_NO_ERROR != err_code ) - { - printf( "OpenGL Error @ %s: %i", calling_location, err_code ); - err_code = glGetError(); - } -} -#else -void check_for_GL_errors( const char *calling_location ) -{ - /* no check for errors */ -} -#endif - -static void createMipmaps(const unsigned char *const img, - int width, int height, int channels, - unsigned int flags, - unsigned int opengl_texture_target, - unsigned int internal_texture_format, - unsigned int original_texture_format, - int DXT_mode) -{ - if ( ( flags & SOIL_FLAG_GL_MIPMAPS ) && query_gen_mipmap_capability() == SOIL_CAPABILITY_PRESENT ) - { - soilGlGenerateMipmap(opengl_texture_target); - } - else - { - int MIPlevel = 1; - int MIPwidth = (width+1) / 2; - int MIPheight = (height+1) / 2; - unsigned char *resampled = (unsigned char*)malloc( channels*MIPwidth*MIPheight ); - - while( ((1< 0; --i ) - { - unsigned char temp = img[index1]; - img[index1] = img[index2]; - img[index2] = temp; - ++index1; - ++index2; - } - } - } - /* does the user want me to scale the colors into the NTSC safe RGB range? */ - if( flags & SOIL_FLAG_NTSC_SAFE_RGB ) - { - scale_image_RGB_to_NTSC_safe( img, iwidth, iheight, channels ); - } - /* does the user want me to convert from straight to pre-multiplied alpha? - (and do we even _have_ alpha?) */ - if( flags & SOIL_FLAG_MULTIPLY_ALPHA ) - { - int i; - switch( channels ) - { - case 2: - for( i = 0; i < 2*iwidth*iheight; i += 2 ) - { - img[i] = (img[i] * img[i+1] + 128) >> 8; - } - break; - case 4: - for( i = 0; i < 4*iwidth*iheight; i += 4 ) - { - img[i+0] = (img[i+0] * img[i+3] + 128) >> 8; - img[i+1] = (img[i+1] * img[i+3] + 128) >> 8; - img[i+2] = (img[i+2] * img[i+3] + 128) >> 8; - } - break; - default: - /* no other number of channels contains alpha data */ - break; - } - } - - /* do I need to make it a power of 2? */ - if( - ( ( flags & SOIL_FLAG_POWER_OF_TWO) && ( !SOIL_IS_POW2(iwidth) || !SOIL_IS_POW2(iheight) ) ) || /* user asked for it and the texture is not power of 2 */ - ( (flags & SOIL_FLAG_MIPMAPS)&& !( ( flags & SOIL_FLAG_GL_MIPMAPS ) && - query_gen_mipmap_capability() == SOIL_CAPABILITY_PRESENT && - query_NPOT_capability() == SOIL_CAPABILITY_PRESENT ) ) || /* need it for the MIP-maps when mipmaps required - and not GL mipmaps required and supported */ - (iwidth > max_supported_size) || /* it's too big, (make sure it's */ - (iheight > max_supported_size) ) /* 2^n for later down-sampling) */ - { - int new_width = 1; - int new_height = 1; - while( new_width < iwidth ) - { - new_width *= 2; - } - while( new_height < iheight ) - { - new_height *= 2; - } - /* still? */ - if( (new_width != iwidth) || (new_height != iheight) ) - { - /* yep, resize */ - unsigned char *resampled = (unsigned char*)malloc( channels*new_width*new_height ); - up_scale_image( - NULL != img ? img : data, iwidth, iheight, channels, - resampled, new_width, new_height ); - - /* nuke the old guy ( if a copy exists ), then point it at the new guy */ - SOIL_free_image_data( img ); - img = resampled; - *width = new_width; - *height = new_height; - iwidth = new_width; - iheight = new_height; - } - } - /* now, if it is too large... */ - if( (iwidth > max_supported_size) || (iheight > max_supported_size) ) - { - /* I've already made it a power of two, so simply use the MIPmapping - code to reduce its size to the allowable maximum. */ - unsigned char *resampled; - int reduce_block_x = 1, reduce_block_y = 1; - int new_width, new_height; - if( iwidth > max_supported_size ) - { - reduce_block_x = iwidth / max_supported_size; - } - if( iheight > max_supported_size ) - { - reduce_block_y = iheight / max_supported_size; - } - new_width = iwidth / reduce_block_x; - new_height = iheight / reduce_block_y; - resampled = (unsigned char*)malloc( channels*new_width*new_height ); - /* perform the actual reduction */ - mipmap_image( NULL != img ? img : data, iwidth, iheight, channels, - resampled, reduce_block_x, reduce_block_y ); - /* nuke the old guy, then point it at the new guy */ - SOIL_free_image_data( img ); - img = resampled; - *width = new_width; - *height = new_height; - iwidth = new_width; - iheight = new_height; - } - /* does the user want us to use YCoCg color space? */ - if( flags & SOIL_FLAG_CoCg_Y ) - { - /* this will only work with RGB and RGBA images */ - convert_RGB_to_YCoCg( img, iwidth, iheight, channels ); - } - /* create the OpenGL texture ID handle - (note: allowing a forced texture ID lets me reload a texture) */ - tex_id = reuse_texture_ID; - if( tex_id == 0 ) - { - glGenTextures( 1, &tex_id ); - } - check_for_GL_errors( "glGenTextures" ); - /* Note: sometimes glGenTextures fails (usually no OpenGL context) */ - if( tex_id ) - { - /* and what type am I using as the internal texture format? */ - switch( channels ) - { - case 1: - original_texture_format = GL_LUMINANCE; - break; - case 2: - original_texture_format = GL_LUMINANCE_ALPHA; - break; - case 3: - original_texture_format = GL_RGB; - break; - case 4: - original_texture_format = GL_RGBA; - break; - } - internal_texture_format = original_texture_format; - /* does the user want me to, and can I, save as DXT? */ - if( flags & SOIL_FLAG_COMPRESS_TO_DXT ) - { - DXT_mode = query_DXT_capability(); - if( DXT_mode == SOIL_CAPABILITY_PRESENT ) - { - /* I can use DXT, whether I compress it or OpenGL does */ - if( (channels & 1) == 1 ) - { - /* 1 or 3 channels = DXT1 */ - internal_texture_format = sRGB_texture ? SOIL_GL_COMPRESSED_SRGB_S3TC_DXT1_EXT : SOIL_RGB_S3TC_DXT1; - } else - { - /* 2 or 4 channels = DXT5 */ - internal_texture_format = sRGB_texture ? SOIL_GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT : SOIL_RGBA_S3TC_DXT5; - } - } - } - else if ( sRGB_texture ) - { - switch( channels ) - { - case 3: - internal_texture_format = SOIL_GL_SRGB; - break; - case 4: - internal_texture_format = SOIL_GL_SRGB_ALPHA; - break; - } - } - - /* bind an OpenGL texture ID */ - glBindTexture( opengl_texture_type, tex_id ); - check_for_GL_errors( "glBindTexture" ); - - /* set the unpack aligment */ - glGetIntegerv(GL_UNPACK_ALIGNMENT, &unpack_aligment); - if ( 1 != unpack_aligment ) - { - glPixelStorei(GL_UNPACK_ALIGNMENT,1); - } - - /* upload the main image */ - if( DXT_mode == SOIL_CAPABILITY_PRESENT ) - { - /* user wants me to do the DXT conversion! */ - int DDS_size; - unsigned char *DDS_data = NULL; - if( (channels & 1) == 1 ) - { - /* RGB, use DXT1 */ - DDS_data = convert_image_to_DXT1( NULL != img ? img : data, iwidth, iheight, channels, &DDS_size ); - } else - { - /* RGBA, use DXT5 */ - DDS_data = convert_image_to_DXT5( NULL != img ? img : data, iwidth, iheight, channels, &DDS_size ); - } - if( DDS_data ) - { - soilGlCompressedTexImage2D( - opengl_texture_target, 0, - internal_texture_format, iwidth, iheight, 0, - DDS_size, DDS_data ); - check_for_GL_errors( "glCompressedTexImage2D" ); - SOIL_free_image_data( DDS_data ); - /* printf( "Internal DXT compressor\n" ); */ - } else - { - /* my compression failed, try the OpenGL driver's version */ - glTexImage2D( - opengl_texture_target, 0, - internal_texture_format, iwidth, iheight, 0, - original_texture_format, GL_UNSIGNED_BYTE, NULL != img ? img : data ); - check_for_GL_errors( "glTexImage2D" ); - /* printf( "OpenGL DXT compressor\n" ); */ - } - } else - { - /* user want OpenGL to do all the work! */ - glTexImage2D( - opengl_texture_target, 0, - internal_texture_format, iwidth, iheight, 0, - original_texture_format, GL_UNSIGNED_BYTE, NULL != img ? img : data ); - - check_for_GL_errors( "glTexImage2D" ); - /*printf( "OpenGL DXT compressor\n" ); */ - } - - /* are any MIPmaps desired? */ - if( flags & SOIL_FLAG_MIPMAPS || flags & SOIL_FLAG_GL_MIPMAPS ) - { - createMipmaps( NULL != img ? img : data, iwidth, iheight, channels, flags, opengl_texture_target, internal_texture_format, original_texture_format, DXT_mode ); - - /* instruct OpenGL to use the MIPmaps */ - glTexParameteri( opengl_texture_type, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); - glTexParameteri( opengl_texture_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR ); - check_for_GL_errors( "GL_TEXTURE_MIN/MAG_FILTER" ); - } else - { - /* instruct OpenGL _NOT_ to use the MIPmaps */ - glTexParameteri( opengl_texture_type, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); - glTexParameteri( opengl_texture_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); - check_for_GL_errors( "GL_TEXTURE_MIN/MAG_FILTER" ); - } - - /* recover the unpack aligment */ - if ( 1 != unpack_aligment ) - { - glPixelStorei(GL_UNPACK_ALIGNMENT, unpack_aligment); - } - - /* does the user want clamping, or wrapping? */ - if( flags & SOIL_FLAG_TEXTURE_REPEATS ) - { - glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_S, GL_REPEAT ); - glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_T, GL_REPEAT ); - if( opengl_texture_type == SOIL_TEXTURE_CUBE_MAP ) - { - /* SOIL_TEXTURE_WRAP_R is invalid if cubemaps aren't supported */ - glTexParameteri( opengl_texture_type, SOIL_TEXTURE_WRAP_R, GL_REPEAT ); - } - check_for_GL_errors( "GL_TEXTURE_WRAP_*" ); - } else - { - unsigned int clamp_mode = SOIL_CLAMP_TO_EDGE; - /* unsigned int clamp_mode = GL_CLAMP; */ - glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_S, clamp_mode ); - glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_T, clamp_mode ); - if( opengl_texture_type == SOIL_TEXTURE_CUBE_MAP ) - { - /* SOIL_TEXTURE_WRAP_R is invalid if cubemaps aren't supported */ - glTexParameteri( opengl_texture_type, SOIL_TEXTURE_WRAP_R, clamp_mode ); - } - check_for_GL_errors( "GL_TEXTURE_WRAP_*" ); - } - /* done */ - result_string_pointer = "Image loaded as an OpenGL texture"; - } else - { - /* failed */ - result_string_pointer = "Failed to generate an OpenGL texture name; missing OpenGL context?"; - } - - SOIL_free_image_data( img ); - - return tex_id; -} - -int - SOIL_save_screenshot - ( - const char *filename, - int image_type, - int x, int y, - int width, int height - ) -{ - unsigned char *pixel_data; - int i, j; - int save_result; - - /* error checks */ - if( (width < 1) || (height < 1) ) - { - result_string_pointer = "Invalid screenshot dimensions"; - return 0; - } - if( (x < 0) || (y < 0) ) - { - result_string_pointer = "Invalid screenshot location"; - return 0; - } - if( filename == NULL ) - { - result_string_pointer = "Invalid screenshot filename"; - return 0; - } - - /* Get the data from OpenGL */ - pixel_data = (unsigned char*)malloc( 3*width*height ); - glReadPixels (x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, pixel_data); - - /* invert the image */ - for( j = 0; j*2 < height; ++j ) - { - int index1 = j * width * 3; - int index2 = (height - 1 - j) * width * 3; - for( i = width * 3; i > 0; --i ) - { - unsigned char temp = pixel_data[index1]; - pixel_data[index1] = pixel_data[index2]; - pixel_data[index2] = temp; - ++index1; - ++index2; - } - } - - /* save the image */ - save_result = SOIL_save_image( filename, image_type, width, height, 3, pixel_data); - - /* And free the memory */ - SOIL_free_image_data( pixel_data ); - return save_result; -} - -unsigned char* - SOIL_load_image - ( - const char *filename, - int *width, int *height, int *channels, - int force_channels - ) -{ - unsigned char *result = stbi_load( filename, - width, height, channels, force_channels ); - if( result == NULL ) - { - result_string_pointer = stbi_failure_reason(); - } else - { - result_string_pointer = "Image loaded"; - } - return result; -} - -unsigned char* - SOIL_load_image_from_memory - ( - const unsigned char *const buffer, - int buffer_length, - int *width, int *height, int *channels, - int force_channels - ) -{ - unsigned char *result = stbi_load_from_memory( - buffer, buffer_length, - width, height, channels, - force_channels ); - if( result == NULL ) - { - result_string_pointer = stbi_failure_reason(); - } else - { - result_string_pointer = "Image loaded from memory"; - } - return result; -} - - -int - SOIL_save_image - ( - const char *filename, - int image_type, - int width, int height, int channels, - const unsigned char *const data - ) -{ - return SOIL_save_image_quality( filename, image_type, width, height, channels, data, 80 ); -} - -int - SOIL_save_image_quality - ( - const char *filename, - int image_type, - int width, int height, int channels, - const unsigned char *const data, - int quality - ) -{ - int save_result; - - /* error check */ - if( (width < 1) || (height < 1) || - (channels < 1) || (channels > 4) || - (data == NULL) || - (filename == NULL) ) - { - return 0; - } - if( image_type == SOIL_SAVE_TYPE_BMP ) - { - save_result = stbi_write_bmp( filename, - width, height, channels, (void*)data ); - } else - if( image_type == SOIL_SAVE_TYPE_TGA ) - { - save_result = stbi_write_tga( filename, - width, height, channels, (void*)data ); - } else - if( image_type == SOIL_SAVE_TYPE_DDS ) - { - save_result = save_image_as_DDS( filename, - width, height, channels, (const unsigned char *const)data ); - } else - if( image_type == SOIL_SAVE_TYPE_PNG ) - { - save_result = stbi_write_png( filename, - width, height, channels, (const unsigned char *const)data, 0 ); - } else - if ( image_type == SOIL_SAVE_TYPE_JPG ) - { - save_result = jo_write_jpg( filename, (const void*)data, width, height, channels, quality ); - } - else - { - save_result = 0; - } - - if( save_result == 0 ) - { - result_string_pointer = "Saving the image failed"; - } else - { - result_string_pointer = "Image saved"; - } - return save_result; -} - -void - SOIL_free_image_data - ( - unsigned char *img_data - ) -{ - if ( img_data ) - free( (void*)img_data ); -} - -const char* - SOIL_last_result - ( - void - ) -{ - return result_string_pointer; -} - -unsigned int SOIL_direct_load_DDS_from_memory( - const unsigned char *const buffer, - int buffer_length, - unsigned int reuse_texture_ID, - int flags, - int loading_as_cubemap ) -{ - /* variables */ - DDS_header header; - unsigned int buffer_index = 0; - unsigned int tex_ID = 0; - /* file reading variables */ - unsigned int S3TC_type = 0; - unsigned char *DDS_data; - unsigned int DDS_main_size; - unsigned int DDS_full_size; - unsigned int width, height; - int mipmaps, cubemap, uncompressed, block_size = 16; - unsigned int flag; - unsigned int cf_target, ogl_target_start, ogl_target_end; - unsigned int opengl_texture_type; - int i; - /* 1st off, does the filename even exist? */ - if( NULL == buffer ) - { - /* we can't do it! */ - result_string_pointer = "NULL buffer"; - return 0; - } - if( buffer_length < sizeof( DDS_header ) ) - { - /* we can't do it! */ - result_string_pointer = "DDS file was too small to contain the DDS header"; - return 0; - } - /* try reading in the header */ - memcpy ( (void*)(&header), (const void *)buffer, sizeof( DDS_header ) ); - buffer_index = sizeof( DDS_header ); - /* guilty until proven innocent */ - result_string_pointer = "Failed to read a known DDS header"; - /* validate the header (warning, "goto"'s ahead, shield your eyes!!) */ - flag = ('D'<<0)|('D'<<8)|('S'<<16)|(' '<<24); - if( header.dwMagic != flag ) {goto quick_exit;} - if( header.dwSize != 124 ) {goto quick_exit;} - /* I need all of these */ - flag = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT; - if( (header.dwFlags & flag) != flag ) {goto quick_exit;} - /* According to the MSDN spec, the dwFlags should contain - DDSD_LINEARSIZE if it's compressed, or DDSD_PITCH if - uncompressed. Some DDS writers do not conform to the - spec, so I need to make my reader more tolerant */ - /* I need one of these */ - flag = DDPF_FOURCC | DDPF_RGB; - if( (header.sPixelFormat.dwFlags & flag) == 0 ) {goto quick_exit;} - if( header.sPixelFormat.dwSize != 32 ) {goto quick_exit;} - if( (header.sCaps.dwCaps1 & DDSCAPS_TEXTURE) == 0 ) {goto quick_exit;} - /* make sure it is a type we can upload */ - if( (header.sPixelFormat.dwFlags & DDPF_FOURCC) && - !( - (header.sPixelFormat.dwFourCC == (('D'<<0)|('X'<<8)|('T'<<16)|('1'<<24))) || - (header.sPixelFormat.dwFourCC == (('D'<<0)|('X'<<8)|('T'<<16)|('3'<<24))) || - (header.sPixelFormat.dwFourCC == (('D'<<0)|('X'<<8)|('T'<<16)|('5'<<24))) - ) ) - { - goto quick_exit; - } - /* OK, validated the header, let's load the image data */ - result_string_pointer = "DDS header loaded and validated"; - width = header.dwWidth; - height = header.dwHeight; - uncompressed = 1 - (header.sPixelFormat.dwFlags & DDPF_FOURCC) / DDPF_FOURCC; - cubemap = (header.sCaps.dwCaps2 & DDSCAPS2_CUBEMAP) / DDSCAPS2_CUBEMAP; - if( uncompressed ) - { - S3TC_type = GL_RGB; - block_size = 3; - if( header.sPixelFormat.dwFlags & DDPF_ALPHAPIXELS ) - { - S3TC_type = GL_RGBA; - block_size = 4; - } - DDS_main_size = width * height * block_size; - } else - { - /* can we even handle direct uploading to OpenGL DXT compressed images? */ - if( query_DXT_capability() != SOIL_CAPABILITY_PRESENT ) - { - /* we can't do it! */ - result_string_pointer = "Direct upload of S3TC images not supported by the OpenGL driver"; - return 0; - } - /* well, we know it is DXT1/3/5, because we checked above */ - switch( (header.sPixelFormat.dwFourCC >> 24) - '0' ) - { - case 1: - S3TC_type = SOIL_RGBA_S3TC_DXT1; - block_size = 8; - break; - case 3: - S3TC_type = SOIL_RGBA_S3TC_DXT3; - block_size = 16; - break; - case 5: - S3TC_type = SOIL_RGBA_S3TC_DXT5; - block_size = 16; - break; - } - DDS_main_size = ((width+3)>>2)*((height+3)>>2)*block_size; - } - if( cubemap ) - { - /* does the user want a cubemap? */ - if( !loading_as_cubemap ) - { - /* we can't do it! */ - result_string_pointer = "DDS image was a cubemap"; - return 0; - } - /* can we even handle cubemaps with the OpenGL driver? */ - if( query_cubemap_capability() != SOIL_CAPABILITY_PRESENT ) - { - /* we can't do it! */ - result_string_pointer = "Direct upload of cubemap images not supported by the OpenGL driver"; - return 0; - } - ogl_target_start = SOIL_TEXTURE_CUBE_MAP_POSITIVE_X; - ogl_target_end = SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Z; - opengl_texture_type = SOIL_TEXTURE_CUBE_MAP; - } else - { - /* does the user want a non-cubemap? */ - if( loading_as_cubemap ) - { - /* we can't do it! */ - result_string_pointer = "DDS image was not a cubemap"; - return 0; - } - ogl_target_start = GL_TEXTURE_2D; - ogl_target_end = GL_TEXTURE_2D; - opengl_texture_type = GL_TEXTURE_2D; - } - if( (header.sCaps.dwCaps1 & DDSCAPS_MIPMAP) && (header.dwMipMapCount > 1) ) - { - mipmaps = header.dwMipMapCount - 1; - DDS_full_size = DDS_main_size; - for( i = 1; i <= mipmaps; ++ i ) - { - int w, h; - w = width >> i; - h = height >> i; - if( w < 1 ) - { - w = 1; - } - if( h < 1 ) - { - h = 1; - } - if ( uncompressed ) - { - /* uncompressed DDS, simple MIPmap size calculation */ - DDS_full_size += w*h*block_size; - } else - { - /* compressed DDS, MIPmap size calculation is block based */ - DDS_full_size += ((w+3)/4)*((h+3)/4)*block_size; - } - } - } else - { - mipmaps = 0; - DDS_full_size = DDS_main_size; - } - DDS_data = (unsigned char*)malloc( DDS_full_size ); - /* got the image data RAM, create or use an existing OpenGL texture handle */ - tex_ID = reuse_texture_ID; - if( tex_ID == 0 ) - { - glGenTextures( 1, &tex_ID ); - } - /* bind an OpenGL texture ID */ - glBindTexture( opengl_texture_type, tex_ID ); - /* do this for each face of the cubemap! */ - for( cf_target = ogl_target_start; cf_target <= ogl_target_end; ++cf_target ) - { - if( buffer_index + DDS_full_size <= (unsigned int)buffer_length ) - { - unsigned int byte_offset = DDS_main_size; - memcpy( (void*)DDS_data, (const void*)(&buffer[buffer_index]), DDS_full_size ); - buffer_index += DDS_full_size; - /* upload the main chunk */ - if( uncompressed ) - { - /* and remember, DXT uncompressed uses BGR(A), - so swap to RGB(A) for ALL MIPmap levels */ - for( i = 0; i < (int)DDS_full_size; i += block_size ) - { - unsigned char temp = DDS_data[i]; - DDS_data[i] = DDS_data[i+2]; - DDS_data[i+2] = temp; - } - glTexImage2D( - cf_target, 0, - S3TC_type, width, height, 0, - S3TC_type, GL_UNSIGNED_BYTE, DDS_data ); - } else - { - soilGlCompressedTexImage2D( - cf_target, 0, - S3TC_type, width, height, 0, - DDS_main_size, DDS_data ); - } - /* upload the mipmaps, if we have them */ - for( i = 1; i <= mipmaps; ++i ) - { - int w, h, mip_size; - w = width >> i; - h = height >> i; - if( w < 1 ) - { - w = 1; - } - if( h < 1 ) - { - h = 1; - } - /* upload this mipmap */ - if( uncompressed ) - { - mip_size = w*h*block_size; - glTexImage2D( - cf_target, i, - S3TC_type, w, h, 0, - S3TC_type, GL_UNSIGNED_BYTE, &DDS_data[byte_offset] ); - } else - { - mip_size = ((w+3)/4)*((h+3)/4)*block_size; - soilGlCompressedTexImage2D( - cf_target, i, - S3TC_type, w, h, 0, - mip_size, &DDS_data[byte_offset] ); - } - /* and move to the next mipmap */ - byte_offset += mip_size; - } - /* it worked! */ - result_string_pointer = "DDS file loaded"; - } else - { - glDeleteTextures( 1, & tex_ID ); - tex_ID = 0; - cf_target = ogl_target_end + 1; - result_string_pointer = "DDS file was too small for expected image data"; - } - }/* end reading each face */ - SOIL_free_image_data( DDS_data ); - if( tex_ID ) - { - /* did I have MIPmaps? */ - if( mipmaps > 0 ) - { - /* instruct OpenGL to use the MIPmaps */ - glTexParameteri( opengl_texture_type, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); - glTexParameteri( opengl_texture_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR ); - } else - { - /* instruct OpenGL _NOT_ to use the MIPmaps */ - glTexParameteri( opengl_texture_type, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); - glTexParameteri( opengl_texture_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); - } - /* does the user want clamping, or wrapping? */ - if( flags & SOIL_FLAG_TEXTURE_REPEATS ) - { - glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_S, GL_REPEAT ); - glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_T, GL_REPEAT ); - glTexParameteri( opengl_texture_type, SOIL_TEXTURE_WRAP_R, GL_REPEAT ); - } else - { - unsigned int clamp_mode = SOIL_CLAMP_TO_EDGE; - /* unsigned int clamp_mode = GL_CLAMP; */ - glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_S, clamp_mode ); - glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_T, clamp_mode ); - glTexParameteri( opengl_texture_type, SOIL_TEXTURE_WRAP_R, clamp_mode ); - } - } - -quick_exit: - /* report success or failure */ - return tex_ID; -} - -unsigned int SOIL_direct_load_DDS( - const char *filename, - unsigned int reuse_texture_ID, - int flags, - int loading_as_cubemap ) -{ - FILE *f; - unsigned char *buffer; - size_t buffer_length, bytes_read; - unsigned int tex_ID = 0; - /* error checks */ - if( NULL == filename ) - { - result_string_pointer = "NULL filename"; - return 0; - } - f = fopen( filename, "rb" ); - if( NULL == f ) - { - /* the file doesn't seem to exist (or be open-able) */ - result_string_pointer = "Can not find DDS file"; - return 0; - } - fseek( f, 0, SEEK_END ); - buffer_length = ftell( f ); - fseek( f, 0, SEEK_SET ); - buffer = (unsigned char *) malloc( buffer_length ); - if( NULL == buffer ) - { - result_string_pointer = "malloc failed"; - fclose( f ); - return 0; - } - bytes_read = fread( (void*)buffer, 1, buffer_length, f ); - fclose( f ); - if( bytes_read < buffer_length ) - { - /* huh? */ - buffer_length = bytes_read; - } - /* now try to do the loading */ - tex_ID = SOIL_direct_load_DDS_from_memory( - (const unsigned char *const)buffer, (int)buffer_length, - reuse_texture_ID, flags, loading_as_cubemap ); - SOIL_free_image_data( buffer ); - return tex_ID; -} - -unsigned int SOIL_direct_load_PVR_from_memory( - const unsigned char *const buffer, - int buffer_length, - unsigned int reuse_texture_ID, - int flags, - int loading_as_cubemap ) -{ - PVR_Texture_Header* header = (PVR_Texture_Header*)buffer; - int num_surfs = 1; - GLuint tex_ID = 0; - GLenum PVR_format = 0; - GLenum PVR_type = GL_RGB; - unsigned int opengl_texture_type = loading_as_cubemap ? SOIL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D; - int is_PVRTC_supported = query_PVR_capability() == SOIL_CAPABILITY_PRESENT; - int is_BGRA8888_supported = query_BGRA8888_capability() == SOIL_CAPABILITY_PRESENT; - int is_compressed_format_supported = 0; - int is_compressed_format = 0; - int mipmaps = 0; - int i; - GLint unpack_aligment; - - // Check the header size - if ( header->dwHeaderSize != sizeof(PVR_Texture_Header) ) { - if ( header->dwHeaderSize == PVRTEX_V1_HEADER_SIZE ) { - result_string_pointer = "this is an old pvr ( update the PVR file )"; - - if ( loading_as_cubemap ) { - if( header->dwpfFlags & PVRTEX_CUBEMAP ) { - num_surfs = 6; - } else { - result_string_pointer = "tried to load a non-cubemap PVR as cubemap"; - return 0; - } - } - } else { - result_string_pointer = "invalid PVR header"; - - return 0; - } - } else { - if ( loading_as_cubemap ) { - // Header V2 - if( header->dwNumSurfs < 1 ) { - if( header->dwpfFlags & PVRTEX_CUBEMAP ) { - num_surfs = 6; - } else { - result_string_pointer = "tried to load a non-cubemap PVR as cubemap"; - return 0; - } - } else { - num_surfs = header->dwNumSurfs; - } - } - } - - // Check the magic identifier - if ( header->dwPVR != PVRTEX_IDENTIFIER ) { - result_string_pointer = "invalid PVR header"; - return 0; - } - - /* Only accept untwiddled data UNLESS texture format is PVRTC */ - if ( ((header->dwpfFlags & PVRTEX_TWIDDLE) == PVRTEX_TWIDDLE) - && ((header->dwpfFlags & PVRTEX_PIXELTYPE)!=OGL_PVRTC2) - && ((header->dwpfFlags & PVRTEX_PIXELTYPE)!=OGL_PVRTC4) ) - { - // We need to load untwiddled textures -- hw will twiddle for us. - result_string_pointer = "pvr is not compressed ( untwiddled texture )"; - return 0; - } - - switch( header->dwpfFlags & PVRTEX_PIXELTYPE ) - { - case OGL_RGBA_4444: - PVR_format = GL_UNSIGNED_SHORT_4_4_4_4; - PVR_type = GL_RGBA; - break; - case OGL_RGBA_5551: - PVR_format = GL_UNSIGNED_SHORT_5_5_5_1; - PVR_type = GL_RGBA; - break; - case OGL_RGBA_8888: - PVR_format = GL_UNSIGNED_BYTE; - PVR_type = GL_RGBA; - break; - case OGL_RGB_565: - PVR_format = GL_UNSIGNED_SHORT_5_6_5; - PVR_type = GL_RGB; - break; - case OGL_RGB_555: - result_string_pointer = "failed: pixel type OGL_RGB_555 not supported."; - return 0; - case OGL_RGB_888: - PVR_format = GL_UNSIGNED_BYTE; - PVR_type = GL_RGB; - break; - case OGL_I_8: - PVR_format = GL_UNSIGNED_BYTE; - PVR_type = GL_LUMINANCE; - break; - case OGL_AI_88: - PVR_format = GL_UNSIGNED_BYTE; - PVR_type = GL_LUMINANCE_ALPHA; - break; - case MGLPT_PVRTC2: - case OGL_PVRTC2: - if(is_PVRTC_supported) { - is_compressed_format_supported = is_compressed_format = 1; - PVR_format = header->dwAlphaBitMask==0 ? SOIL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG : SOIL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG ; // PVRTC2 - } else { - result_string_pointer = "error: PVRTC2 not supported.Decompress the texture first."; - return 0; - } - break; - case MGLPT_PVRTC4: - case OGL_PVRTC4: - if(is_PVRTC_supported) { - is_compressed_format_supported = is_compressed_format = 1; - PVR_format = header->dwAlphaBitMask==0 ? SOIL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG : SOIL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG ; // PVRTC4 - } else { - result_string_pointer = "error: PVRTC4 not supported. Decompress the texture first."; - return 0; - } - break; - case OGL_BGRA_8888: - if(is_BGRA8888_supported) { - PVR_format = GL_UNSIGNED_BYTE; - PVR_type = GL_BGRA; - break; - } else { - result_string_pointer = "Unable to load GL_BGRA texture as extension GL_IMG_texture_format_BGRA8888 is unsupported."; - return 0; - } - default: // NOT SUPPORTED - result_string_pointer = "failed: pixel type not supported."; - return 0; - } - - #ifdef SOIL_GLES1 - // check that this data is cube map data or not. - if( loading_as_cubemap ) { - result_string_pointer = "cube map textures are not available in GLES1.x."; - return 0; - } - #endif - - // load the texture up - tex_ID = reuse_texture_ID; - if( tex_ID == 0 ) - { - glGenTextures( 1, &tex_ID ); - } - - glBindTexture( opengl_texture_type, tex_ID ); - - if( glGetError() ) { - result_string_pointer = "failed: glBindTexture() failed."; - return 0; - } - - glGetIntegerv(GL_UNPACK_ALIGNMENT, &unpack_aligment); - if ( 1 != unpack_aligment ) - { - glPixelStorei(GL_UNPACK_ALIGNMENT,1); // Never have row-aligned in headers - } - - #define _MAX( a, b ) (( a <= b )? b : a) - for(i=0; idwHeaderSize + header->dwTextureDataSize * i; - char *cur_texture_ptr = 0; - int mipmap_level; - unsigned int width= header->dwWidth, height = header->dwHeight; - unsigned int compressed_image_size = 0; - - mipmaps = ( ( flags & SOIL_FLAG_MIPMAPS ) && (header->dwpfFlags & PVRTEX_MIPMAP) ) ? header->dwMipMapCount : 0; - - for(mipmap_level = 0; mipmap_level <= mipmaps; width = _MAX(width/2, (unsigned int)1), height = _MAX(height/2, (unsigned int)1), mipmap_level++ ) { - // Do Alpha-swap if needed - cur_texture_ptr = texture_ptr; - - // Load the Texture - /* If the texture is PVRTC then use GLCompressedTexImage2D */ - if( is_compressed_format ) { - /* Calculate how many bytes this MIP level occupies */ - if ((header->dwpfFlags & PVRTEX_PIXELTYPE)==OGL_PVRTC2) { - compressed_image_size = ( _MAX(width, PVRTC2_MIN_TEXWIDTH) * _MAX(height, PVRTC2_MIN_TEXHEIGHT) * header->dwBitCount + 7 ) / 8; - } else {// PVRTC4 case - compressed_image_size = ( _MAX(width, PVRTC4_MIN_TEXWIDTH) * _MAX(height, PVRTC4_MIN_TEXHEIGHT) * header->dwBitCount + 7 ) / 8; - } - - if ( is_compressed_format_supported ) { - /* Load compressed texture data at selected MIP level */ - if ( loading_as_cubemap ) { - soilGlCompressedTexImage2D( SOIL_TEXTURE_CUBE_MAP_POSITIVE_X + i, mipmap_level, PVR_format, width, height, 0, compressed_image_size, cur_texture_ptr ); - } else { - soilGlCompressedTexImage2D( opengl_texture_type, mipmap_level, PVR_format, width, height, 0, compressed_image_size, cur_texture_ptr ); - } - } else { - result_string_pointer = "failed: GPU doesnt support compressed textures"; - } - } else { - /* Load uncompressed texture data at selected MIP level */ - if ( loading_as_cubemap ) { - glTexImage2D( SOIL_TEXTURE_CUBE_MAP_POSITIVE_X + i, mipmap_level, PVR_type, width, height, 0, PVR_type, PVR_format, cur_texture_ptr ); - } else { - glTexImage2D( opengl_texture_type, mipmap_level, PVR_type, width, height, 0, PVR_type, PVR_format, cur_texture_ptr ); - } - } - - if( glGetError() ) { - result_string_pointer = "failed: glCompressedTexImage2D() failed."; - if ( 1 != unpack_aligment ) - { - glPixelStorei(GL_UNPACK_ALIGNMENT, unpack_aligment); - } - return 0; - } - - // offset the texture pointer by one mip-map level - /* PVRTC case */ - if ( is_compressed_format ) { - texture_ptr += compressed_image_size; - } else { - /* New formula that takes into account bit counts inferior to 8 (e.g. 1 bpp) */ - texture_ptr += (width * height * header->dwBitCount + 7) / 8; - } - } - } - #undef _MAX - - if ( 1 != unpack_aligment ) - { - glPixelStorei(GL_UNPACK_ALIGNMENT, unpack_aligment); - } - - if( tex_ID ) - { - /* did I have MIPmaps? */ - if( mipmaps ) - { - /* instruct OpenGL to use the MIPmaps */ - glTexParameteri( opengl_texture_type, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); - glTexParameteri( opengl_texture_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR ); - } else - { - /* instruct OpenGL _NOT_ to use the MIPmaps */ - glTexParameteri( opengl_texture_type, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); - glTexParameteri( opengl_texture_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); - } - - /* does the user want clamping, or wrapping? */ - if( flags & SOIL_FLAG_TEXTURE_REPEATS ) - { - glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_S, GL_REPEAT ); - glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_T, GL_REPEAT ); - glTexParameteri( opengl_texture_type, SOIL_TEXTURE_WRAP_R, GL_REPEAT ); - } else - { - unsigned int clamp_mode = SOIL_CLAMP_TO_EDGE; - /* unsigned int clamp_mode = GL_CLAMP; */ - glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_S, clamp_mode ); - glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_T, clamp_mode ); - glTexParameteri( opengl_texture_type, SOIL_TEXTURE_WRAP_R, clamp_mode ); - } - } - - return tex_ID; -} - -unsigned int SOIL_direct_load_PVR( - const char *filename, - unsigned int reuse_texture_ID, - int flags, - int loading_as_cubemap ) -{ - FILE *f; - unsigned char *buffer; - size_t buffer_length, bytes_read; - unsigned int tex_ID = 0; - /* error checks */ - if( NULL == filename ) - { - result_string_pointer = "NULL filename"; - return 0; - } - f = fopen( filename, "rb" ); - if( NULL == f ) - { - /* the file doesn't seem to exist (or be open-able) */ - result_string_pointer = "Can not find PVR file"; - return 0; - } - fseek( f, 0, SEEK_END ); - buffer_length = ftell( f ); - fseek( f, 0, SEEK_SET ); - buffer = (unsigned char *) malloc( buffer_length ); - if( NULL == buffer ) - { - result_string_pointer = "malloc failed"; - fclose( f ); - return 0; - } - bytes_read = fread( (void*)buffer, 1, buffer_length, f ); - fclose( f ); - if( bytes_read < buffer_length ) - { - /* huh? */ - buffer_length = bytes_read; - } - /* now try to do the loading */ - tex_ID = SOIL_direct_load_PVR_from_memory( - (const unsigned char *const)buffer, (int)buffer_length, - reuse_texture_ID, flags, loading_as_cubemap ); - SOIL_free_image_data( buffer ); - return tex_ID; -} - -unsigned int SOIL_direct_load_ETC1_from_memory( - const unsigned char *const buffer, - int buffer_length, - unsigned int reuse_texture_ID, - int flags ) -{ - GLuint tex_ID = 0; - PKMHeader* header = (PKMHeader*)buffer; - unsigned int opengl_texture_type = GL_TEXTURE_2D; - unsigned int width; - unsigned int height; - unsigned long compressed_image_size = buffer_length - PKM_HEADER_SIZE; - char *texture_ptr = (char*)buffer + PKM_HEADER_SIZE; - GLint unpack_aligment; - - if ( query_ETC1_capability() != SOIL_CAPABILITY_PRESENT ) { - result_string_pointer = "error: ETC1 not supported. Decompress the texture first."; - return 0; - } - - if ( 0 != strcmp( header->aName, "PKM 10" ) ) { - result_string_pointer = "error: PKM 10 header not found."; - return 0; - } - - width = (header->iWidthMSB << 8) | header->iWidthLSB; - height = (header->iHeightMSB << 8) | header->iHeightLSB; - compressed_image_size = (((width + 3) & ~3) * ((height + 3) & ~3)) >> 1; - - // load the texture up - tex_ID = reuse_texture_ID; - if( tex_ID == 0 ) - { - glGenTextures( 1, &tex_ID ); - } - - glBindTexture( opengl_texture_type, tex_ID ); - - if( glGetError() ) { - result_string_pointer = "failed: glBindTexture() failed."; - return 0; - } - - glGetIntegerv(GL_UNPACK_ALIGNMENT, &unpack_aligment); - if ( 1 != unpack_aligment ) - { - glPixelStorei(GL_UNPACK_ALIGNMENT,1); // Never have row-aligned in headers - } - - soilGlCompressedTexImage2D( opengl_texture_type, 0, SOIL_GL_ETC1_RGB8_OES, width, height, 0, compressed_image_size, texture_ptr ); - - if( glGetError() ) { - result_string_pointer = "failed: glCompressedTexImage2D() failed."; - - if ( 1 != unpack_aligment ) - { - glPixelStorei(GL_UNPACK_ALIGNMENT, unpack_aligment); - } - return 0; - } - - if ( 1 != unpack_aligment ) - { - glPixelStorei(GL_UNPACK_ALIGNMENT, unpack_aligment); - } - - if( tex_ID ) - { - /* No MIPmaps for ETC1 */ - glTexParameteri( opengl_texture_type, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); - glTexParameteri( opengl_texture_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); - - /* does the user want clamping, or wrapping? */ - if( flags & SOIL_FLAG_TEXTURE_REPEATS ) - { - glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_S, GL_REPEAT ); - glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_T, GL_REPEAT ); - glTexParameteri( opengl_texture_type, SOIL_TEXTURE_WRAP_R, GL_REPEAT ); - } else - { - unsigned int clamp_mode = SOIL_CLAMP_TO_EDGE; - /* unsigned int clamp_mode = GL_CLAMP; */ - glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_S, clamp_mode ); - glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_T, clamp_mode ); - glTexParameteri( opengl_texture_type, SOIL_TEXTURE_WRAP_R, clamp_mode ); - } - } - - return tex_ID; -} - -unsigned int SOIL_direct_load_ETC1(const char *filename, - unsigned int reuse_texture_ID, - int flags ) -{ - FILE *f; - unsigned char *buffer; - size_t buffer_length, bytes_read; - unsigned int tex_ID = 0; - /* error checks */ - if( NULL == filename ) - { - result_string_pointer = "NULL filename"; - return 0; - } - f = fopen( filename, "rb" ); - if( NULL == f ) - { - /* the file doesn't seem to exist (or be open-able) */ - result_string_pointer = "Can not find PVR file"; - return 0; - } - fseek( f, 0, SEEK_END ); - buffer_length = ftell( f ); - fseek( f, 0, SEEK_SET ); - buffer = (unsigned char *) malloc( buffer_length ); - if( NULL == buffer ) - { - result_string_pointer = "malloc failed"; - fclose( f ); - return 0; - } - bytes_read = fread( (void*)buffer, 1, buffer_length, f ); - fclose( f ); - if( bytes_read < buffer_length ) - { - /* huh? */ - buffer_length = bytes_read; - } - /* now try to do the loading */ - tex_ID = SOIL_direct_load_ETC1_from_memory( - (const unsigned char *const)buffer, (int)buffer_length, - reuse_texture_ID, flags ); - SOIL_free_image_data( buffer ); - return tex_ID; -} - -int query_NPOT_capability( void ) -{ - /* check for the capability */ - if( has_NPOT_capability == SOIL_CAPABILITY_UNKNOWN ) - { - /* we haven't yet checked for the capability, do so */ - if( - (0 == SOIL_GL_ExtensionSupported( - "GL_ARB_texture_non_power_of_two" ) ) - && - (0 == SOIL_GL_ExtensionSupported( - "GL_OES_texture_npot" ) ) - ) - { - /* not there, flag the failure */ - has_NPOT_capability = SOIL_CAPABILITY_NONE; - } else - { - /* it's there! */ - has_NPOT_capability = SOIL_CAPABILITY_PRESENT; - } - - #if defined( __emscripten__ ) || defined( EMSCRIPTEN ) - has_NPOT_capability = SOIL_CAPABILITY_PRESENT; - #endif - } - /* let the user know if we can do non-power-of-two textures or not */ - return has_NPOT_capability; -} - -int query_tex_rectangle_capability( void ) -{ - /* check for the capability */ - if( has_tex_rectangle_capability == SOIL_CAPABILITY_UNKNOWN ) - { - /* we haven't yet checked for the capability, do so */ - if( - (0 == SOIL_GL_ExtensionSupported( - "GL_ARB_texture_rectangle" ) ) - && - (0 == SOIL_GL_ExtensionSupported( - "GL_EXT_texture_rectangle" ) ) - && - (0 == SOIL_GL_ExtensionSupported( - "GL_NV_texture_rectangle" ) ) - ) - { - /* not there, flag the failure */ - has_tex_rectangle_capability = SOIL_CAPABILITY_NONE; - } else - { - /* it's there! */ - has_tex_rectangle_capability = SOIL_CAPABILITY_PRESENT; - } - } - /* let the user know if we can do texture rectangles or not */ - return has_tex_rectangle_capability; -} - -int query_cubemap_capability( void ) -{ - /* check for the capability */ - if( has_cubemap_capability == SOIL_CAPABILITY_UNKNOWN ) - { - /* we haven't yet checked for the capability, do so */ - if( - (0 == SOIL_GL_ExtensionSupported( - "GL_ARB_texture_cube_map" ) ) - && - (0 == SOIL_GL_ExtensionSupported( - "GL_EXT_texture_cube_map" ) ) - ) - { - /* not there, flag the failure */ - has_cubemap_capability = SOIL_CAPABILITY_NONE; - } else - { - /* it's there! */ - has_cubemap_capability = SOIL_CAPABILITY_PRESENT; - } - } - /* let the user know if we can do cubemaps or not */ - return has_cubemap_capability; -} - -static P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC get_glCompressedTexImage2D_addr() -{ - /* and find the address of the extension function */ - P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC ext_addr = NULL; - -#if defined( SOIL_PLATFORM_WIN32 ) || defined( SOIL_PLATFORM_OSX ) || defined( SOIL_X11_PLATFORM ) - ext_addr = (P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC)SOIL_GL_GetProcAddress( "glCompressedTexImage2D" ); -#else - ext_addr = (P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC)&glCompressedTexImage2D; -#endif - - return ext_addr; -} - -int query_DXT_capability( void ) -{ - /* check for the capability */ - if( has_DXT_capability == SOIL_CAPABILITY_UNKNOWN ) - { - /* we haven't yet checked for the capability, do so */ - if ( 0 == SOIL_GL_ExtensionSupported( - "GL_EXT_texture_compression_s3tc" ) && - 0 == SOIL_GL_ExtensionSupported( - "WEBGL_compressed_texture_s3tc ") && - 0 == SOIL_GL_ExtensionSupported( - "WEBKIT_WEBGL_compressed_texture_s3tc") && - 0 == SOIL_GL_ExtensionSupported( - "MOZ_WEBGL_compressed_texture_s3tc" - ) - ) - { - /* not there, flag the failure */ - has_DXT_capability = SOIL_CAPABILITY_NONE; - } else - { - P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC ext_addr = get_glCompressedTexImage2D_addr(); - - /* Flag it so no checks needed later */ - if( NULL == ext_addr ) - { - /* hmm, not good!! This should not happen, but does on my - laptop's VIA chipset. The GL_EXT_texture_compression_s3tc - spec requires that ARB_texture_compression be present too. - this means I can upload and have the OpenGL drive do the - conversion, but I can't use my own routines or load DDS files - from disk and upload them directly [8^( */ - has_DXT_capability = SOIL_CAPABILITY_NONE; - } else - { - /* all's well! */ - soilGlCompressedTexImage2D = ext_addr; - has_DXT_capability = SOIL_CAPABILITY_PRESENT; - } - } - } - /* let the user know if we can do DXT or not */ - return has_DXT_capability; -} - -int query_PVR_capability( void ) -{ - /* check for the capability */ - if( has_PVR_capability == SOIL_CAPABILITY_UNKNOWN ) - { - /* we haven't yet checked for the capability, do so */ - if (0 == SOIL_GL_ExtensionSupported( - "GL_IMG_texture_compression_pvrtc" ) ) - { - /* not there, flag the failure */ - has_PVR_capability = SOIL_CAPABILITY_NONE; - } else - { - if ( NULL == soilGlCompressedTexImage2D ) { - soilGlCompressedTexImage2D = get_glCompressedTexImage2D_addr(); - } - - /* it's there! */ - has_PVR_capability = SOIL_CAPABILITY_PRESENT; - } - } - /* let the user know if we can do cubemaps or not */ - return has_PVR_capability; -} - -int query_BGRA8888_capability( void ) -{ - /* check for the capability */ - if( has_BGRA8888_capability == SOIL_CAPABILITY_UNKNOWN ) - { - /* we haven't yet checked for the capability, do so */ - if (0 == SOIL_GL_ExtensionSupported( - "GL_IMG_texture_format_BGRA8888" ) ) - { - /* not there, flag the failure */ - has_BGRA8888_capability = SOIL_CAPABILITY_NONE; - } else - { - /* it's there! */ - has_BGRA8888_capability = SOIL_CAPABILITY_PRESENT; - } - } - /* let the user know if we can do cubemaps or not */ - return has_BGRA8888_capability; -} - -int query_sRGB_capability( void ) -{ - if ( has_sRGB_capability == SOIL_CAPABILITY_UNKNOWN ) - { - if (0 == SOIL_GL_ExtensionSupported( - "GL_EXT_texture_sRGB" ) - && - 0 == SOIL_GL_ExtensionSupported( - "GL_EXT_sRGB" ) - && - 0 == SOIL_GL_ExtensionSupported( - "EXT_sRGB" ) ) - { - has_sRGB_capability = SOIL_CAPABILITY_NONE; - } else - { - has_sRGB_capability = SOIL_CAPABILITY_PRESENT; - } - } - - return has_sRGB_capability; -} - -int query_ETC1_capability( void ) -{ - /* check for the capability */ - if( has_ETC1_capability == SOIL_CAPABILITY_UNKNOWN ) - { - /* we haven't yet checked for the capability, do so */ - if (0 == SOIL_GL_ExtensionSupported( - "GL_OES_compressed_ETC1_RGB8_texture" ) ) - { - /* not there, flag the failure */ - has_ETC1_capability = SOIL_CAPABILITY_NONE; - } else - { - if ( NULL == soilGlCompressedTexImage2D ) { - soilGlCompressedTexImage2D = get_glCompressedTexImage2D_addr(); - } - - /* it's there! */ - has_ETC1_capability = SOIL_CAPABILITY_PRESENT; - } - } - /* let the user know if we can do cubemaps or not */ - return has_ETC1_capability; -} - -int query_gen_mipmap_capability( void ) -{ - /* check for the capability */ - P_SOIL_GLGENERATEMIPMAPPROC ext_addr = NULL; - - if( has_gen_mipmap_capability == SOIL_CAPABILITY_UNKNOWN ) - { - if ( 0 == SOIL_GL_ExtensionSupported( - "GL_ARB_framebuffer_object" ) - && - 0 == SOIL_GL_ExtensionSupported( - "GL_EXT_framebuffer_object" ) - && 0 == SOIL_GL_ExtensionSupported( - "GL_OES_framebuffer_object" ) - ) - { - /* not there, flag the failure */ - has_gen_mipmap_capability = SOIL_CAPABILITY_NONE; - } - else - { - #if !defined( SOIL_GLES1 ) && !defined( SOIL_GLES2 ) - - ext_addr = (P_SOIL_GLGENERATEMIPMAPPROC)SOIL_GL_GetProcAddress("glGenerateMipmap"); - - if(ext_addr == NULL) - { - ext_addr = (P_SOIL_GLGENERATEMIPMAPPROC)SOIL_GL_GetProcAddress("glGenerateMipmapEXT"); - } - - #elif !defined( SOIL_NO_EGL ) - - ext_addr = (P_SOIL_GLGENERATEMIPMAPPROC)SOIL_GL_GetProcAddress("glGenerateMipmapOES"); - - if(ext_addr == NULL) - { - ext_addr = (P_SOIL_GLGENERATEMIPMAPPROC)SOIL_GL_GetProcAddress("glGenerateMipmap"); - } - - #elif defined( SOIL_GLES2 ) - ext_addr = &glGenerateMipmap; - #else /** SOIL_GLES1 */ - ext_addr = &glGenerateMipmapOES; - #endif - } - - if(ext_addr == NULL) - { - /* this should never happen */ - has_gen_mipmap_capability = SOIL_CAPABILITY_NONE; - } else - { - /* it's there! */ - has_gen_mipmap_capability = SOIL_CAPABILITY_PRESENT; - soilGlGenerateMipmap = ext_addr; - } - } - - return has_gen_mipmap_capability; -} diff --git a/lib/SOIL2/SOIL2.h b/lib/SOIL2/SOIL2.h deleted file mode 100644 index eace403cf..000000000 --- a/lib/SOIL2/SOIL2.h +++ /dev/null @@ -1,511 +0,0 @@ -/** - @mainpage SOIL2 - - Fork by Martin Lucas Golini - - Original author Jonathan Dummer - 2007-07-26-10.36 - - Simple OpenGL Image Library 2 - - A tiny c library for uploading images as - textures into OpenGL. Also saving and - loading of images is supported. - - I'm using Sean's Tool Box image loader as a base: - http://www.nothings.org/ - - I'm upgrading it to load TGA and DDS files, and a direct - path for loading DDS files straight into OpenGL textures, - when applicable. - - Image Formats: - - BMP load & save - - TGA load & save - - DDS load & save - - PNG load & save - - JPG load & save - - PSD load - - HDR load - - PIC load - - OpenGL Texture Features: - - resample to power-of-two sizes - - MIPmap generation - - compressed texture S3TC formats (if supported) - - can pre-multiply alpha for you, for better compositing - - can flip image about the y-axis (except pre-compressed DDS files) - - Thanks to: - * Sean Barret - for the awesome stb_image - * Dan Venkitachalam - for finding some non-compliant DDS files, and patching some explicit casts - * everybody at gamedev.net -**/ - -#ifndef HEADER_SIMPLE_OPENGL_IMAGE_LIBRARY -#define HEADER_SIMPLE_OPENGL_IMAGE_LIBRARY - -#ifdef __cplusplus -extern "C" { -#endif - -/** - The format of images that may be loaded (force_channels). - SOIL_LOAD_AUTO leaves the image in whatever format it was found. - SOIL_LOAD_L forces the image to load as Luminous (greyscale) - SOIL_LOAD_LA forces the image to load as Luminous with Alpha - SOIL_LOAD_RGB forces the image to load as Red Green Blue - SOIL_LOAD_RGBA forces the image to load as Red Green Blue Alpha -**/ -enum -{ - SOIL_LOAD_AUTO = 0, - SOIL_LOAD_L = 1, - SOIL_LOAD_LA = 2, - SOIL_LOAD_RGB = 3, - SOIL_LOAD_RGBA = 4 -}; - -/** - Passed in as reuse_texture_ID, will cause SOIL to - register a new texture ID using glGenTextures(). - If the value passed into reuse_texture_ID > 0 then - SOIL will just re-use that texture ID (great for - reloading image assets in-game!) -**/ -enum -{ - SOIL_CREATE_NEW_ID = 0 -}; - -/** - flags you can pass into SOIL_load_OGL_texture() - and SOIL_create_OGL_texture(). - (note that if SOIL_FLAG_DDS_LOAD_DIRECT is used - the rest of the flags with the exception of - SOIL_FLAG_TEXTURE_REPEATS will be ignored while - loading already-compressed DDS files.) - - SOIL_FLAG_POWER_OF_TWO: force the image to be POT - SOIL_FLAG_MIPMAPS: generate mipmaps for the texture - SOIL_FLAG_TEXTURE_REPEATS: otherwise will clamp - SOIL_FLAG_MULTIPLY_ALPHA: for using (GL_ONE,GL_ONE_MINUS_SRC_ALPHA) blending - SOIL_FLAG_INVERT_Y: flip the image vertically - SOIL_FLAG_COMPRESS_TO_DXT: if the card can display them, will convert RGB to DXT1, RGBA to DXT5 - SOIL_FLAG_DDS_LOAD_DIRECT: will load DDS files directly without _ANY_ additional processing ( if supported ) - SOIL_FLAG_NTSC_SAFE_RGB: clamps RGB components to the range [16,235] - SOIL_FLAG_CoCg_Y: Google YCoCg; RGB=>CoYCg, RGBA=>CoCgAY - SOIL_FLAG_TEXTURE_RECTANGE: uses ARB_texture_rectangle ; pixel indexed & no repeat or MIPmaps or cubemaps - SOIL_FLAG_PVR_LOAD_DIRECT: will load PVR files directly without _ANY_ additional processing ( if supported ) -**/ -enum -{ - SOIL_FLAG_POWER_OF_TWO = 1, - SOIL_FLAG_MIPMAPS = 2, - SOIL_FLAG_TEXTURE_REPEATS = 4, - SOIL_FLAG_MULTIPLY_ALPHA = 8, - SOIL_FLAG_INVERT_Y = 16, - SOIL_FLAG_COMPRESS_TO_DXT = 32, - SOIL_FLAG_DDS_LOAD_DIRECT = 64, - SOIL_FLAG_NTSC_SAFE_RGB = 128, - SOIL_FLAG_CoCg_Y = 256, - SOIL_FLAG_TEXTURE_RECTANGLE = 512, - SOIL_FLAG_PVR_LOAD_DIRECT = 1024, - SOIL_FLAG_ETC1_LOAD_DIRECT = 2048, - SOIL_FLAG_GL_MIPMAPS = 4096, - SOIL_FLAG_SRGB_COLOR_SPACE = 8192 -}; - -/** - The types of images that may be saved. - (TGA supports uncompressed RGB / RGBA) - (BMP supports uncompressed RGB) - (DDS supports DXT1 and DXT5) - (PNG supports RGB / RGBA) -**/ -enum -{ - SOIL_SAVE_TYPE_TGA = 0, - SOIL_SAVE_TYPE_BMP = 1, - SOIL_SAVE_TYPE_PNG = 2, - SOIL_SAVE_TYPE_DDS = 3, - SOIL_SAVE_TYPE_JPG = 4 -}; - -/** - Defines the order of faces in a DDS cubemap. - I recommend that you use the same order in single - image cubemap files, so they will be interchangeable - with DDS cubemaps when using SOIL. -**/ -#define SOIL_DDS_CUBEMAP_FACE_ORDER "EWUDNS" - -/** - The types of internal fake HDR representations - - SOIL_HDR_RGBE: RGB * pow( 2.0, A - 128.0 ) - SOIL_HDR_RGBdivA: RGB / A - SOIL_HDR_RGBdivA2: RGB / (A*A) -**/ -enum -{ - SOIL_HDR_RGBE = 0, - SOIL_HDR_RGBdivA = 1, - SOIL_HDR_RGBdivA2 = 2 -}; - -/** - Loads an image from disk into an OpenGL texture. - \param filename the name of the file to upload as a texture - \param force_channels 0-image format, 1-luminous, 2-luminous/alpha, 3-RGB, 4-RGBA - \param reuse_texture_ID 0-generate a new texture ID, otherwise reuse the texture ID (overwriting the old texture) - \param flags can be any of SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | SOIL_FLAG_TEXTURE_REPEATS | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT | SOIL_FLAG_DDS_LOAD_DIRECT - \return 0-failed, otherwise returns the OpenGL texture handle -**/ -unsigned int - SOIL_load_OGL_texture - ( - const char *filename, - int force_channels, - unsigned int reuse_texture_ID, - unsigned int flags - ); - -/** - Loads 6 images from disk into an OpenGL cubemap texture. - \param x_pos_file the name of the file to upload as the +x cube face - \param x_neg_file the name of the file to upload as the -x cube face - \param y_pos_file the name of the file to upload as the +y cube face - \param y_neg_file the name of the file to upload as the -y cube face - \param z_pos_file the name of the file to upload as the +z cube face - \param z_neg_file the name of the file to upload as the -z cube face - \param force_channels 0-image format, 1-luminous, 2-luminous/alpha, 3-RGB, 4-RGBA - \param reuse_texture_ID 0-generate a new texture ID, otherwise reuse the texture ID (overwriting the old texture) - \param flags can be any of SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | SOIL_FLAG_TEXTURE_REPEATS | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT | SOIL_FLAG_DDS_LOAD_DIRECT - \return 0-failed, otherwise returns the OpenGL texture handle -**/ -unsigned int - SOIL_load_OGL_cubemap - ( - const char *x_pos_file, - const char *x_neg_file, - const char *y_pos_file, - const char *y_neg_file, - const char *z_pos_file, - const char *z_neg_file, - int force_channels, - unsigned int reuse_texture_ID, - unsigned int flags - ); - -/** - Loads 1 image from disk and splits it into an OpenGL cubemap texture. - \param filename the name of the file to upload as a texture - \param face_order the order of the faces in the file, any combination of NSWEUD, for North, South, Up, etc. - \param force_channels 0-image format, 1-luminous, 2-luminous/alpha, 3-RGB, 4-RGBA - \param reuse_texture_ID 0-generate a new texture ID, otherwise reuse the texture ID (overwriting the old texture) - \param flags can be any of SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | SOIL_FLAG_TEXTURE_REPEATS | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT | SOIL_FLAG_DDS_LOAD_DIRECT - \return 0-failed, otherwise returns the OpenGL texture handle -**/ -unsigned int - SOIL_load_OGL_single_cubemap - ( - const char *filename, - const char face_order[6], - int force_channels, - unsigned int reuse_texture_ID, - unsigned int flags - ); - -/** - Loads an HDR image from disk into an OpenGL texture. - \param filename the name of the file to upload as a texture - \param fake_HDR_format SOIL_HDR_RGBE, SOIL_HDR_RGBdivA, SOIL_HDR_RGBdivA2 - \param reuse_texture_ID 0-generate a new texture ID, otherwise reuse the texture ID (overwriting the old texture) - \param flags can be any of SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | SOIL_FLAG_TEXTURE_REPEATS | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT - \return 0-failed, otherwise returns the OpenGL texture handle -**/ -unsigned int - SOIL_load_OGL_HDR_texture - ( - const char *filename, - int fake_HDR_format, - int rescale_to_max, - unsigned int reuse_texture_ID, - unsigned int flags - ); - -/** - Loads an image from RAM into an OpenGL texture. - \param buffer the image data in RAM just as if it were still in a file - \param buffer_length the size of the buffer in bytes - \param force_channels 0-image format, 1-luminous, 2-luminous/alpha, 3-RGB, 4-RGBA - \param reuse_texture_ID 0-generate a new texture ID, otherwise reuse the texture ID (overwriting the old texture) - \param flags can be any of SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | SOIL_FLAG_TEXTURE_REPEATS | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT | SOIL_FLAG_DDS_LOAD_DIRECT - \return 0-failed, otherwise returns the OpenGL texture handle -**/ -unsigned int - SOIL_load_OGL_texture_from_memory - ( - const unsigned char *const buffer, - int buffer_length, - int force_channels, - unsigned int reuse_texture_ID, - unsigned int flags - ); - -/** - Loads 6 images from memory into an OpenGL cubemap texture. - \param x_pos_buffer the image data in RAM to upload as the +x cube face - \param x_pos_buffer_length the size of the above buffer - \param x_neg_buffer the image data in RAM to upload as the +x cube face - \param x_neg_buffer_length the size of the above buffer - \param y_pos_buffer the image data in RAM to upload as the +x cube face - \param y_pos_buffer_length the size of the above buffer - \param y_neg_buffer the image data in RAM to upload as the +x cube face - \param y_neg_buffer_length the size of the above buffer - \param z_pos_buffer the image data in RAM to upload as the +x cube face - \param z_pos_buffer_length the size of the above buffer - \param z_neg_buffer the image data in RAM to upload as the +x cube face - \param z_neg_buffer_length the size of the above buffer - \param force_channels 0-image format, 1-luminous, 2-luminous/alpha, 3-RGB, 4-RGBA - \param reuse_texture_ID 0-generate a new texture ID, otherwise reuse the texture ID (overwriting the old texture) - \param flags can be any of SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | SOIL_FLAG_TEXTURE_REPEATS | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT | SOIL_FLAG_DDS_LOAD_DIRECT - \return 0-failed, otherwise returns the OpenGL texture handle -**/ -unsigned int - SOIL_load_OGL_cubemap_from_memory - ( - const unsigned char *const x_pos_buffer, - int x_pos_buffer_length, - const unsigned char *const x_neg_buffer, - int x_neg_buffer_length, - const unsigned char *const y_pos_buffer, - int y_pos_buffer_length, - const unsigned char *const y_neg_buffer, - int y_neg_buffer_length, - const unsigned char *const z_pos_buffer, - int z_pos_buffer_length, - const unsigned char *const z_neg_buffer, - int z_neg_buffer_length, - int force_channels, - unsigned int reuse_texture_ID, - unsigned int flags - ); - -/** - Loads 1 image from RAM and splits it into an OpenGL cubemap texture. - \param buffer the image data in RAM just as if it were still in a file - \param buffer_length the size of the buffer in bytes - \param face_order the order of the faces in the file, any combination of NSWEUD, for North, South, Up, etc. - \param force_channels 0-image format, 1-luminous, 2-luminous/alpha, 3-RGB, 4-RGBA - \param reuse_texture_ID 0-generate a new texture ID, otherwise reuse the texture ID (overwriting the old texture) - \param flags can be any of SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | SOIL_FLAG_TEXTURE_REPEATS | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT | SOIL_FLAG_DDS_LOAD_DIRECT - \return 0-failed, otherwise returns the OpenGL texture handle -**/ -unsigned int - SOIL_load_OGL_single_cubemap_from_memory - ( - const unsigned char *const buffer, - int buffer_length, - const char face_order[6], - int force_channels, - unsigned int reuse_texture_ID, - unsigned int flags - ); - -/** - Creates a 2D OpenGL texture from raw image data. Note that the raw data is - _NOT_ freed after the upload (so the user can load various versions). - \param data the raw data to be uploaded as an OpenGL texture - \param width the pointer of the width of the image in pixels ( if the texture size change, width will be overrided with the new width ) - \param height the pointer of the height of the image in pixels ( if the texture size change, height will be overrided with the new height ) - \param channels the number of channels: 1-luminous, 2-luminous/alpha, 3-RGB, 4-RGBA - \param reuse_texture_ID 0-generate a new texture ID, otherwise reuse the texture ID (overwriting the old texture) - \param flags can be any of SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | SOIL_FLAG_TEXTURE_REPEATS | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT - \return 0-failed, otherwise returns the OpenGL texture handle -**/ -unsigned int - SOIL_create_OGL_texture - ( - const unsigned char *const data, - int *width, int *height, int channels, - unsigned int reuse_texture_ID, - unsigned int flags - ); - -/** - Creates an OpenGL cubemap texture by splitting up 1 image into 6 parts. - \param data the raw data to be uploaded as an OpenGL texture - \param width the width of the image in pixels - \param height the height of the image in pixels - \param channels the number of channels: 1-luminous, 2-luminous/alpha, 3-RGB, 4-RGBA - \param face_order the order of the faces in the file, and combination of NSWEUD, for North, South, Up, etc. - \param reuse_texture_ID 0-generate a new texture ID, otherwise reuse the texture ID (overwriting the old texture) - \param flags can be any of SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | SOIL_FLAG_TEXTURE_REPEATS | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT | SOIL_FLAG_DDS_LOAD_DIRECT - \return 0-failed, otherwise returns the OpenGL texture handle -**/ -unsigned int - SOIL_create_OGL_single_cubemap - ( - const unsigned char *const data, - int width, int height, int channels, - const char face_order[6], - unsigned int reuse_texture_ID, - unsigned int flags - ); - -/** - Captures the OpenGL window (RGB) and saves it to disk - \return 0 if it failed, otherwise returns 1 -**/ -int - SOIL_save_screenshot - ( - const char *filename, - int image_type, - int x, int y, - int width, int height - ); - -/** - Loads an image from disk into an array of unsigned chars. - Note that *channels return the original channel count of the - image. If force_channels was other than SOIL_LOAD_AUTO, - the resulting image has force_channels, but *channels may be - different (if the original image had a different channel - count). - \return 0 if failed, otherwise returns 1 -**/ -unsigned char* - SOIL_load_image - ( - const char *filename, - int *width, int *height, int *channels, - int force_channels - ); - -/** - Loads an image from memory into an array of unsigned chars. - Note that *channels return the original channel count of the - image. If force_channels was other than SOIL_LOAD_AUTO, - the resulting image has force_channels, but *channels may be - different (if the original image had a different channel - count). - \return 0 if failed, otherwise returns 1 -**/ -unsigned char* - SOIL_load_image_from_memory - ( - const unsigned char *const buffer, - int buffer_length, - int *width, int *height, int *channels, - int force_channels - ); - -/** - Saves an image from an array of unsigned chars (RGBA) to disk - \param quality parameter only used for SOIL_SAVE_TYPE_JPG files, values accepted between 0 and 100. - \return 0 if failed, otherwise returns 1 -**/ -int - SOIL_save_image_quality - ( - const char *filename, - int image_type, - int width, int height, int channels, - const unsigned char *const data, - int quality - ); - -int - SOIL_save_image - ( - const char *filename, - int image_type, - int width, int height, int channels, - const unsigned char *const data - ); - -/** - Frees the image data (note, this is just C's "free()"...this function is - present mostly so C++ programmers don't forget to use "free()" and call - "delete []" instead [8^) -**/ -void - SOIL_free_image_data - ( - unsigned char *img_data - ); - -/** - This function resturn a pointer to a string describing the last thing - that happened inside SOIL. It can be used to determine why an image - failed to load. -**/ -const char* - SOIL_last_result - ( - void - ); - -/** @return The address of the GL function proc, or NULL if the function is not found. */ -void * - SOIL_GL_GetProcAddress - ( - const char *proc - ); - -/** @return 1 if an OpenGL extension is supported for the current context, 0 otherwise. */ -int - SOIL_GL_ExtensionSupported - ( - const char *extension - ); - -/** Loads the DDS texture directly to the GPU memory ( if supported ) */ -unsigned int SOIL_direct_load_DDS( - const char *filename, - unsigned int reuse_texture_ID, - int flags, - int loading_as_cubemap ); - -/** Loads the DDS texture directly to the GPU memory ( if supported ) */ -unsigned int SOIL_direct_load_DDS_from_memory( - const unsigned char *const buffer, - int buffer_length, - unsigned int reuse_texture_ID, - int flags, - int loading_as_cubemap ); - -/** Loads the PVR texture directly to the GPU memory ( if supported ) */ -unsigned int SOIL_direct_load_PVR( - const char *filename, - unsigned int reuse_texture_ID, - int flags, - int loading_as_cubemap ); - -/** Loads the PVR texture directly to the GPU memory ( if supported ) */ -unsigned int SOIL_direct_load_PVR_from_memory( - const unsigned char *const buffer, - int buffer_length, - unsigned int reuse_texture_ID, - int flags, - int loading_as_cubemap ); - -/** Loads the PVR texture directly to the GPU memory ( if supported ) */ -unsigned int SOIL_direct_load_ETC1(const char *filename, - unsigned int reuse_texture_ID, - int flags ); - -/** Loads the PVR texture directly to the GPU memory ( if supported ) */ -unsigned int SOIL_direct_load_ETC1_from_memory(const unsigned char *const buffer, - int buffer_length, - unsigned int reuse_texture_ID, - int flags ); - -#ifdef __cplusplus -} -#endif - -#endif /* HEADER_SIMPLE_OPENGL_IMAGE_LIBRARY */ diff --git a/lib/SOIL2/etc1_utils.c b/lib/SOIL2/etc1_utils.c deleted file mode 100644 index 1d10f0e2b..000000000 --- a/lib/SOIL2/etc1_utils.c +++ /dev/null @@ -1,680 +0,0 @@ -// Copyright 2009 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "etc1_utils.h" - -#include - -/* From http://www.khronos.org/registry/gles/extensions/OES/OES_compressed_ETC1_RGB8_texture.txt - - The number of bits that represent a 4x4 texel block is 64 bits if - is given by ETC1_RGB8_OES. - - The data for a block is a number of bytes, - - {q0, q1, q2, q3, q4, q5, q6, q7} - - where byte q0 is located at the lowest memory address and q7 at - the highest. The 64 bits specifying the block is then represented - by the following 64 bit integer: - - int64bit = 256*(256*(256*(256*(256*(256*(256*q0+q1)+q2)+q3)+q4)+q5)+q6)+q7; - - ETC1_RGB8_OES: - - a) bit layout in bits 63 through 32 if diffbit = 0 - - 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 - ----------------------------------------------- - | base col1 | base col2 | base col1 | base col2 | - | R1 (4bits)| R2 (4bits)| G1 (4bits)| G2 (4bits)| - ----------------------------------------------- - - 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 - --------------------------------------------------- - | base col1 | base col2 | table | table |diff|flip| - | B1 (4bits)| B2 (4bits)| cw 1 | cw 2 |bit |bit | - --------------------------------------------------- - - - b) bit layout in bits 63 through 32 if diffbit = 1 - - 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 - ----------------------------------------------- - | base col1 | dcol 2 | base col1 | dcol 2 | - | R1' (5 bits) | dR2 | G1' (5 bits) | dG2 | - ----------------------------------------------- - - 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 - --------------------------------------------------- - | base col 1 | dcol 2 | table | table |diff|flip| - | B1' (5 bits) | dB2 | cw 1 | cw 2 |bit |bit | - --------------------------------------------------- - - - c) bit layout in bits 31 through 0 (in both cases) - - 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 - ----------------------------------------------- - | most significant pixel index bits | - | p| o| n| m| l| k| j| i| h| g| f| e| d| c| b| a| - ----------------------------------------------- - - 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 - -------------------------------------------------- - | least significant pixel index bits | - | p| o| n| m| l| k| j| i| h| g| f| e| d| c | b | a | - -------------------------------------------------- - - - Add table 3.17.2: Intensity modifier sets for ETC1 compressed textures: - - table codeword modifier table - ------------------ ---------------------- - 0 -8 -2 2 8 - 1 -17 -5 5 17 - 2 -29 -9 9 29 - 3 -42 -13 13 42 - 4 -60 -18 18 60 - 5 -80 -24 24 80 - 6 -106 -33 33 106 - 7 -183 -47 47 183 - - - Add table 3.17.3 Mapping from pixel index values to modifier values for - ETC1 compressed textures: - - pixel index value - --------------- - msb lsb resulting modifier value - ----- ----- ------------------------- - 1 1 -b (large negative value) - 1 0 -a (small negative value) - 0 0 a (small positive value) - 0 1 b (large positive value) - - - */ - -static const int kModifierTable[] = { -/* 0 */2, 8, -2, -8, -/* 1 */5, 17, -5, -17, -/* 2 */9, 29, -9, -29, -/* 3 */13, 42, -13, -42, -/* 4 */18, 60, -18, -60, -/* 5 */24, 80, -24, -80, -/* 6 */33, 106, -33, -106, -/* 7 */47, 183, -47, -183 }; - -static const int kLookup[8] = { 0, 1, 2, 3, -4, -3, -2, -1 }; - -static inline etc1_byte clamp(int x) { - return (etc1_byte) (x >= 0 ? (x < 255 ? x : 255) : 0); -} - -static -inline int convert4To8(int b) { - int c = b & 0xf; - return (c << 4) | c; -} - -static -inline int convert5To8(int b) { - int c = b & 0x1f; - return (c << 3) | (c >> 2); -} - -static -inline int convert6To8(int b) { - int c = b & 0x3f; - return (c << 2) | (c >> 4); -} - -static -inline int divideBy255(int d) { - return (d + 128 + (d >> 8)) >> 8; -} - -static -inline int convert8To4(int b) { - //int c = b & 0xff; - return divideBy255(b * 15); -} - -static -inline int convert8To5(int b) { - //int c = b & 0xff; - return divideBy255(b * 31); -} - -static -inline int convertDiff(int base, int diff) { - return convert5To8((0x1f & base) + kLookup[0x7 & diff]); -} - -static -void decode_subblock(etc1_byte* pOut, int r, int g, int b, const int* table, - etc1_uint32 low, etc1_bool second, etc1_bool flipped) { - int baseX = 0; - int baseY = 0; - int i; - - if (second) { - if (flipped) { - baseY = 2; - } else { - baseX = 2; - } - } - for (i = 0; i < 8; i++) { - int x, y; - if (flipped) { - x = baseX + (i >> 1); - y = baseY + (i & 1); - } else { - x = baseX + (i >> 2); - y = baseY + (i & 3); - } - int k = y + (x * 4); - int offset = ((low >> k) & 1) | ((low >> (k + 15)) & 2); - int delta = table[offset]; - etc1_byte* q = pOut + 3 * (x + 4 * y); - *q++ = clamp(r + delta); - *q++ = clamp(g + delta); - *q++ = clamp(b + delta); - } -} - -// Input is an ETC1 compressed version of the data. -// Output is a 4 x 4 square of 3-byte pixels in form R, G, B - -void etc1_decode_block(const etc1_byte* pIn, etc1_byte* pOut) { - etc1_uint32 high = (pIn[0] << 24) | (pIn[1] << 16) | (pIn[2] << 8) | pIn[3]; - etc1_uint32 low = (pIn[4] << 24) | (pIn[5] << 16) | (pIn[6] << 8) | pIn[7]; - int r1, r2, g1, g2, b1, b2; - if (high & 2) { - // differential - int rBase = high >> 27; - int gBase = high >> 19; - int bBase = high >> 11; - r1 = convert5To8(rBase); - r2 = convertDiff(rBase, high >> 24); - g1 = convert5To8(gBase); - g2 = convertDiff(gBase, high >> 16); - b1 = convert5To8(bBase); - b2 = convertDiff(bBase, high >> 8); - } else { - // not differential - r1 = convert4To8(high >> 28); - r2 = convert4To8(high >> 24); - g1 = convert4To8(high >> 20); - g2 = convert4To8(high >> 16); - b1 = convert4To8(high >> 12); - b2 = convert4To8(high >> 8); - } - int tableIndexA = 7 & (high >> 5); - int tableIndexB = 7 & (high >> 2); - const int* tableA = kModifierTable + tableIndexA * 4; - const int* tableB = kModifierTable + tableIndexB * 4; - etc1_bool flipped = (high & 1) != 0; - decode_subblock(pOut, r1, g1, b1, tableA, low, 0, flipped); - decode_subblock(pOut, r2, g2, b2, tableB, low, 1, flipped); -} - -typedef struct { - etc1_uint32 high; - etc1_uint32 low; - etc1_uint32 score; // Lower is more accurate -} etc_compressed; - -static -inline void take_best(etc_compressed* a, const etc_compressed* b) { - if (a->score > b->score) { - *a = *b; - } -} - -static -void etc_average_colors_subblock(const etc1_byte* pIn, etc1_uint32 inMask, - etc1_byte* pColors, etc1_bool flipped, etc1_bool second) { - int r = 0; - int g = 0; - int b = 0; - int y, x; - - if (flipped) { - int by = 0; - if (second) { - by = 2; - } - for ( y = 0; y < 2; y++) { - int yy = by + y; - for ( x = 0; x < 4; x++) { - int i = x + 4 * yy; - if (inMask & (1 << i)) { - const etc1_byte* p = pIn + i * 3; - r += *(p++); - g += *(p++); - b += *(p++); - } - } - } - } else { - int bx = 0; - if (second) { - bx = 2; - } - for ( y = 0; y < 4; y++) { - for ( x = 0; x < 2; x++) { - int xx = bx + x; - int i = xx + 4 * y; - if (inMask & (1 << i)) { - const etc1_byte* p = pIn + i * 3; - r += *(p++); - g += *(p++); - b += *(p++); - } - } - } - } - pColors[0] = (etc1_byte)((r + 4) >> 3); - pColors[1] = (etc1_byte)((g + 4) >> 3); - pColors[2] = (etc1_byte)((b + 4) >> 3); -} - -static -inline int square(int x) { - return x * x; -} - -static etc1_uint32 chooseModifier(const etc1_byte* pBaseColors, - const etc1_byte* pIn, etc1_uint32 *pLow, int bitIndex, - const int* pModifierTable) { - etc1_uint32 bestScore = ~0; - int bestIndex = 0; - int pixelR = pIn[0]; - int pixelG = pIn[1]; - int pixelB = pIn[2]; - int r = pBaseColors[0]; - int g = pBaseColors[1]; - int b = pBaseColors[2]; - int i; - for ( i = 0; i < 4; i++) { - int modifier = pModifierTable[i]; - int decodedG = clamp(g + modifier); - etc1_uint32 score = (etc1_uint32) (6 * square(decodedG - pixelG)); - if (score >= bestScore) { - continue; - } - int decodedR = clamp(r + modifier); - score += (etc1_uint32) (3 * square(decodedR - pixelR)); - if (score >= bestScore) { - continue; - } - int decodedB = clamp(b + modifier); - score += (etc1_uint32) square(decodedB - pixelB); - if (score < bestScore) { - bestScore = score; - bestIndex = i; - } - } - etc1_uint32 lowMask = (((bestIndex >> 1) << 16) | (bestIndex & 1)) - << bitIndex; - *pLow |= lowMask; - return bestScore; -} - -static -void etc_encode_subblock_helper(const etc1_byte* pIn, etc1_uint32 inMask, - etc_compressed* pCompressed, etc1_bool flipped, etc1_bool second, - const etc1_byte* pBaseColors, const int* pModifierTable) { - int score = pCompressed->score; - int y, x; - if (flipped) { - int by = 0; - if (second) { - by = 2; - } - for ( y = 0; y < 2; y++) { - int yy = by + y; - for ( x = 0; x < 4; x++) { - int i = x + 4 * yy; - if (inMask & (1 << i)) { - score += chooseModifier(pBaseColors, pIn + i * 3, - &pCompressed->low, yy + x * 4, pModifierTable); - } - } - } - } else { - int bx = 0; - if (second) { - bx = 2; - } - for ( y = 0; y < 4; y++) { - for ( x = 0; x < 2; x++) { - int xx = bx + x; - int i = xx + 4 * y; - if (inMask & (1 << i)) { - score += chooseModifier(pBaseColors, pIn + i * 3, - &pCompressed->low, y + xx * 4, pModifierTable); - } - } - } - } - pCompressed->score = score; -} - -static etc1_bool inRange4bitSigned(int color) { - return color >= -4 && color <= 3; -} - -static void etc_encodeBaseColors(etc1_byte* pBaseColors, - const etc1_byte* pColors, etc_compressed* pCompressed) { - int r1, g1, b1, r2 = 0, g2 = 0, b2 = 0; // 8 bit base colors for sub-blocks - etc1_bool differential; - { - int r51 = convert8To5(pColors[0]); - int g51 = convert8To5(pColors[1]); - int b51 = convert8To5(pColors[2]); - int r52 = convert8To5(pColors[3]); - int g52 = convert8To5(pColors[4]); - int b52 = convert8To5(pColors[5]); - - r1 = convert5To8(r51); - g1 = convert5To8(g51); - b1 = convert5To8(b51); - - int dr = r52 - r51; - int dg = g52 - g51; - int db = b52 - b51; - - differential = inRange4bitSigned(dr) && inRange4bitSigned(dg) - && inRange4bitSigned(db); - if (differential) { - r2 = convert5To8(r51 + dr); - g2 = convert5To8(g51 + dg); - b2 = convert5To8(b51 + db); - pCompressed->high |= (r51 << 27) | ((7 & dr) << 24) | (g51 << 19) - | ((7 & dg) << 16) | (b51 << 11) | ((7 & db) << 8) | 2; - } - } - - if (!differential) { - int r41 = convert8To4(pColors[0]); - int g41 = convert8To4(pColors[1]); - int b41 = convert8To4(pColors[2]); - int r42 = convert8To4(pColors[3]); - int g42 = convert8To4(pColors[4]); - int b42 = convert8To4(pColors[5]); - r1 = convert4To8(r41); - g1 = convert4To8(g41); - b1 = convert4To8(b41); - r2 = convert4To8(r42); - g2 = convert4To8(g42); - b2 = convert4To8(b42); - pCompressed->high |= (r41 << 28) | (r42 << 24) | (g41 << 20) | (g42 - << 16) | (b41 << 12) | (b42 << 8); - } - pBaseColors[0] = r1; - pBaseColors[1] = g1; - pBaseColors[2] = b1; - pBaseColors[3] = r2; - pBaseColors[4] = g2; - pBaseColors[5] = b2; -} - -static -void etc_encode_block_helper(const etc1_byte* pIn, etc1_uint32 inMask, - const etc1_byte* pColors, etc_compressed* pCompressed, etc1_bool flipped) { - int i; - - pCompressed->score = ~0; - pCompressed->high = (flipped ? 1 : 0); - pCompressed->low = 0; - - etc1_byte pBaseColors[6]; - - etc_encodeBaseColors(pBaseColors, pColors, pCompressed); - - int originalHigh = pCompressed->high; - - const int* pModifierTable = kModifierTable; - for ( i = 0; i < 8; i++, pModifierTable += 4) { - etc_compressed temp; - temp.score = 0; - temp.high = originalHigh | (i << 5); - temp.low = 0; - etc_encode_subblock_helper(pIn, inMask, &temp, flipped, 0, - pBaseColors, pModifierTable); - take_best(pCompressed, &temp); - } - pModifierTable = kModifierTable; - etc_compressed firstHalf = *pCompressed; - for ( i = 0; i < 8; i++, pModifierTable += 4) { - etc_compressed temp; - temp.score = firstHalf.score; - temp.high = firstHalf.high | (i << 2); - temp.low = firstHalf.low; - etc_encode_subblock_helper(pIn, inMask, &temp, flipped, 1, - pBaseColors + 3, pModifierTable); - if (i == 0) { - *pCompressed = temp; - } else { - take_best(pCompressed, &temp); - } - } -} - -static void writeBigEndian(etc1_byte* pOut, etc1_uint32 d) { - pOut[0] = (etc1_byte)(d >> 24); - pOut[1] = (etc1_byte)(d >> 16); - pOut[2] = (etc1_byte)(d >> 8); - pOut[3] = (etc1_byte) d; -} - -// Input is a 4 x 4 square of 3-byte pixels in form R, G, B -// inmask is a 16-bit mask where bit (1 << (x + y * 4)) tells whether the corresponding (x,y) -// pixel is valid or not. Invalid pixel color values are ignored when compressing. -// Output is an ETC1 compressed version of the data. - -void etc1_encode_block(const etc1_byte* pIn, etc1_uint32 inMask, - etc1_byte* pOut) { - etc1_byte colors[6]; - etc1_byte flippedColors[6]; - etc_average_colors_subblock(pIn, inMask, colors, 0, 0); - etc_average_colors_subblock(pIn, inMask, colors + 3, 0, 1); - etc_average_colors_subblock(pIn, inMask, flippedColors, 1, 0); - etc_average_colors_subblock(pIn, inMask, flippedColors + 3, 1, 1); - - etc_compressed a, b; - etc_encode_block_helper(pIn, inMask, colors, &a, 0); - etc_encode_block_helper(pIn, inMask, flippedColors, &b, 1); - take_best(&a, &b); - writeBigEndian(pOut, a.high); - writeBigEndian(pOut + 4, a.low); -} - -// Return the size of the encoded image data (does not include size of PKM header). - -etc1_uint32 etc1_get_encoded_data_size(etc1_uint32 width, etc1_uint32 height) { - return (((width + 3) & ~3) * ((height + 3) & ~3)) >> 1; -} - -// Encode an entire image. -// pIn - pointer to the image data. Formatted such that the Red component of -// pixel (x,y) is at pIn + pixelSize * x + stride * y + redOffset; -// pOut - pointer to encoded data. Must be large enough to store entire encoded image. - -int etc1_encode_image(const etc1_byte* pIn, etc1_uint32 width, etc1_uint32 height, - etc1_uint32 pixelSize, etc1_uint32 stride, etc1_byte* pOut) { - if (pixelSize < 2 || pixelSize > 3) { - return -1; - } - static const unsigned short kYMask[] = { 0x0, 0xf, 0xff, 0xfff, 0xffff }; - static const unsigned short kXMask[] = { 0x0, 0x1111, 0x3333, 0x7777, - 0xffff }; - etc1_byte block[ETC1_DECODED_BLOCK_SIZE]; - etc1_byte encoded[ETC1_ENCODED_BLOCK_SIZE]; - etc1_uint32 y, x, cy, cx; - - etc1_uint32 encodedWidth = (width + 3) & ~3; - etc1_uint32 encodedHeight = (height + 3) & ~3; - - for ( y = 0; y < encodedHeight; y += 4) { - etc1_uint32 yEnd = height - y; - if (yEnd > 4) { - yEnd = 4; - } - int ymask = kYMask[yEnd]; - for ( x = 0; x < encodedWidth; x += 4) { - etc1_uint32 xEnd = width - x; - if (xEnd > 4) { - xEnd = 4; - } - int mask = ymask & kXMask[xEnd]; - for ( cy = 0; cy < yEnd; cy++) { - etc1_byte* q = block + (cy * 4) * 3; - const etc1_byte* p = pIn + pixelSize * x + stride * (y + cy); - if (pixelSize == 3) { - memcpy(q, p, xEnd * 3); - } else { - for ( cx = 0; cx < xEnd; cx++) { - int pixel = (p[1] << 8) | p[0]; - *q++ = convert5To8(pixel >> 11); - *q++ = convert6To8(pixel >> 5); - *q++ = convert5To8(pixel); - p += pixelSize; - } - } - } - etc1_encode_block(block, mask, encoded); - memcpy(pOut, encoded, sizeof(encoded)); - pOut += sizeof(encoded); - } - } - return 0; -} - -// Decode an entire image. -// pIn - pointer to encoded data. -// pOut - pointer to the image data. Will be written such that the Red component of -// pixel (x,y) is at pIn + pixelSize * x + stride * y + redOffset. Must be -// large enough to store entire image. - - -int etc1_decode_image(const etc1_byte* pIn, etc1_byte* pOut, - etc1_uint32 width, etc1_uint32 height, - etc1_uint32 pixelSize, etc1_uint32 stride) { - if (pixelSize < 2 || pixelSize > 3) { - return -1; - } - etc1_byte block[ETC1_DECODED_BLOCK_SIZE]; - - etc1_uint32 encodedWidth = (width + 3) & ~3; - etc1_uint32 encodedHeight = (height + 3) & ~3; - - etc1_uint32 y, x, cy, cx; - - for ( y = 0; y < encodedHeight; y += 4) { - etc1_uint32 yEnd = height - y; - if (yEnd > 4) { - yEnd = 4; - } - for ( x = 0; x < encodedWidth; x += 4) { - etc1_uint32 xEnd = width - x; - if (xEnd > 4) { - xEnd = 4; - } - etc1_decode_block(pIn, block); - pIn += ETC1_ENCODED_BLOCK_SIZE; - for ( cy = 0; cy < yEnd; cy++) { - const etc1_byte* q = block + (cy * 4) * 3; - etc1_byte* p = pOut + pixelSize * x + stride * (y + cy); - if (pixelSize == 3) { - memcpy(p, q, xEnd * 3); - } else { - for ( cx = 0; cx < xEnd; cx++) { - etc1_byte r = *q++; - etc1_byte g = *q++; - etc1_byte b = *q++; - etc1_uint32 pixel = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3); - *p++ = (etc1_byte) pixel; - *p++ = (etc1_byte) (pixel >> 8); - } - } - } - } - } - return 0; -} - -static const char kMagic[] = { 'P', 'K', 'M', ' ', '1', '0' }; - -static const etc1_uint32 ETC1_PKM_FORMAT_OFFSET = 6; -static const etc1_uint32 ETC1_PKM_ENCODED_WIDTH_OFFSET = 8; -static const etc1_uint32 ETC1_PKM_ENCODED_HEIGHT_OFFSET = 10; -static const etc1_uint32 ETC1_PKM_WIDTH_OFFSET = 12; -static const etc1_uint32 ETC1_PKM_HEIGHT_OFFSET = 14; - -static const etc1_uint32 ETC1_RGB_NO_MIPMAPS = 0; - -static void writeBEUint16(etc1_byte* pOut, etc1_uint32 data) { - pOut[0] = (etc1_byte) (data >> 8); - pOut[1] = (etc1_byte) data; -} - -static etc1_uint32 readBEUint16(const etc1_byte* pIn) { - return (pIn[0] << 8) | pIn[1]; -} - -// Format a PKM header - -void etc1_pkm_format_header(etc1_byte* pHeader, etc1_uint32 width, etc1_uint32 height) { - memcpy(pHeader, kMagic, sizeof(kMagic)); - etc1_uint32 encodedWidth = (width + 3) & ~3; - etc1_uint32 encodedHeight = (height + 3) & ~3; - writeBEUint16(pHeader + ETC1_PKM_FORMAT_OFFSET, ETC1_RGB_NO_MIPMAPS); - writeBEUint16(pHeader + ETC1_PKM_ENCODED_WIDTH_OFFSET, encodedWidth); - writeBEUint16(pHeader + ETC1_PKM_ENCODED_HEIGHT_OFFSET, encodedHeight); - writeBEUint16(pHeader + ETC1_PKM_WIDTH_OFFSET, width); - writeBEUint16(pHeader + ETC1_PKM_HEIGHT_OFFSET, height); -} - -// Check if a PKM header is correctly formatted. - -etc1_bool etc1_pkm_is_valid(const etc1_byte* pHeader) { - if (memcmp(pHeader, kMagic, sizeof(kMagic))) { - return 0; - } - etc1_uint32 format = readBEUint16(pHeader + ETC1_PKM_FORMAT_OFFSET); - etc1_uint32 encodedWidth = readBEUint16(pHeader + ETC1_PKM_ENCODED_WIDTH_OFFSET); - etc1_uint32 encodedHeight = readBEUint16(pHeader + ETC1_PKM_ENCODED_HEIGHT_OFFSET); - etc1_uint32 width = readBEUint16(pHeader + ETC1_PKM_WIDTH_OFFSET); - etc1_uint32 height = readBEUint16(pHeader + ETC1_PKM_HEIGHT_OFFSET); - return format == ETC1_RGB_NO_MIPMAPS && - encodedWidth >= width && encodedWidth - width < 4 && - encodedHeight >= height && encodedHeight - height < 4; -} - -// Read the image width from a PKM header - -etc1_uint32 etc1_pkm_get_width(const etc1_byte* pHeader) { - return readBEUint16(pHeader + ETC1_PKM_WIDTH_OFFSET); -} - -// Read the image height from a PKM header - -etc1_uint32 etc1_pkm_get_height(const etc1_byte* pHeader){ - return readBEUint16(pHeader + ETC1_PKM_HEIGHT_OFFSET); -} diff --git a/lib/SOIL2/etc1_utils.h b/lib/SOIL2/etc1_utils.h deleted file mode 100644 index 4bee00c24..000000000 --- a/lib/SOIL2/etc1_utils.h +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2009 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef __etc1_h__ -#define __etc1_h__ - -#define ETC1_ENCODED_BLOCK_SIZE 8 -#define ETC1_DECODED_BLOCK_SIZE 48 - -#ifndef ETC1_RGB8_OES -#define ETC1_RGB8_OES 0x8D64 -#endif - -typedef unsigned char etc1_byte; -typedef int etc1_bool; -typedef unsigned int etc1_uint32; - -#ifdef __cplusplus -extern "C" { -#endif - -// Encode a block of pixels. -// -// pIn is a pointer to a ETC_DECODED_BLOCK_SIZE array of bytes that represent a -// 4 x 4 square of 3-byte pixels in form R, G, B. Byte (3 * (x + 4 * y) is the R -// value of pixel (x, y). -// -// validPixelMask is a 16-bit mask where bit (1 << (x + y * 4)) indicates whether -// the corresponding (x,y) pixel is valid. Invalid pixel color values are ignored when compressing. -// -// pOut is an ETC1 compressed version of the data. - -void etc1_encode_block(const etc1_byte* pIn, etc1_uint32 validPixelMask, etc1_byte* pOut); - -// Decode a block of pixels. -// -// pIn is an ETC1 compressed version of the data. -// -// pOut is a pointer to a ETC_DECODED_BLOCK_SIZE array of bytes that represent a -// 4 x 4 square of 3-byte pixels in form R, G, B. Byte (3 * (x + 4 * y) is the R -// value of pixel (x, y). - -void etc1_decode_block(const etc1_byte* pIn, etc1_byte* pOut); - -// Return the size of the encoded image data (does not include size of PKM header). - -etc1_uint32 etc1_get_encoded_data_size(etc1_uint32 width, etc1_uint32 height); - -// Encode an entire image. -// pIn - pointer to the image data. Formatted such that -// pixel (x,y) is at pIn + pixelSize * x + stride * y; -// pOut - pointer to encoded data. Must be large enough to store entire encoded image. -// pixelSize can be 2 or 3. 2 is an GL_UNSIGNED_SHORT_5_6_5 image, 3 is a GL_BYTE RGB image. -// returns non-zero if there is an error. - -int etc1_encode_image(const etc1_byte* pIn, etc1_uint32 width, etc1_uint32 height, - etc1_uint32 pixelSize, etc1_uint32 stride, etc1_byte* pOut); - -// Decode an entire image. -// pIn - pointer to encoded data. -// pOut - pointer to the image data. Will be written such that -// pixel (x,y) is at pIn + pixelSize * x + stride * y. Must be -// large enough to store entire image. -// pixelSize can be 2 or 3. 2 is an GL_UNSIGNED_SHORT_5_6_5 image, 3 is a GL_BYTE RGB image. -// returns non-zero if there is an error. - -int etc1_decode_image(const etc1_byte* pIn, etc1_byte* pOut, - etc1_uint32 width, etc1_uint32 height, - etc1_uint32 pixelSize, etc1_uint32 stride); - -// Size of a PKM header, in bytes. - -#define ETC_PKM_HEADER_SIZE 16 - -// Format a PKM header - -void etc1_pkm_format_header(etc1_byte* pHeader, etc1_uint32 width, etc1_uint32 height); - -// Check if a PKM header is correctly formatted. - -etc1_bool etc1_pkm_is_valid(const etc1_byte* pHeader); - -// Read the image width from a PKM header - -etc1_uint32 etc1_pkm_get_width(const etc1_byte* pHeader); - -// Read the image height from a PKM header - -etc1_uint32 etc1_pkm_get_height(const etc1_byte* pHeader); - -#ifdef __cplusplus -} -#endif - -#endif \ No newline at end of file diff --git a/lib/SOIL2/image_DXT.c b/lib/SOIL2/image_DXT.c deleted file mode 100644 index 4206a1bbc..000000000 --- a/lib/SOIL2/image_DXT.c +++ /dev/null @@ -1,632 +0,0 @@ -/* - Jonathan Dummer - 2007-07-31-10.32 - - simple DXT compression / decompression code - - public domain -*/ - -#include "image_DXT.h" -#include -#include -#include -#include - -/* set this =1 if you want to use the covarince matrix method... - which is better than my method of using standard deviations - overall, except on the infintesimal chance that the power - method fails for finding the largest eigenvector */ -#define USE_COV_MAT 1 - -/********* Function Prototypes *********/ -/* - Takes a 4x4 block of pixels and compresses it into 8 bytes - in DXT1 format (color only, no alpha). Speed is valued - over prettyness, at least for now. -*/ -void compress_DDS_color_block( - int channels, - const unsigned char *const uncompressed, - unsigned char compressed[8] ); -/* - Takes a 4x4 block of pixels and compresses the alpha - component it into 8 bytes for use in DXT5 DDS files. - Speed is valued over prettyness, at least for now. -*/ -void compress_DDS_alpha_block( - const unsigned char *const uncompressed, - unsigned char compressed[8] ); - -/********* Actual Exposed Functions *********/ -int - save_image_as_DDS - ( - const char *filename, - int width, int height, int channels, - const unsigned char *const data - ) -{ - /* variables */ - FILE *fout; - unsigned char *DDS_data; - DDS_header header; - int DDS_size; - /* error check */ - if( (NULL == filename) || - (width < 1) || (height < 1) || - (channels < 1) || (channels > 4) || - (data == NULL ) ) - { - return 0; - } - /* Convert the image */ - if( (channels & 1) == 1 ) - { - /* no alpha, just use DXT1 */ - DDS_data = convert_image_to_DXT1( data, width, height, channels, &DDS_size ); - } else - { - /* has alpha, so use DXT5 */ - DDS_data = convert_image_to_DXT5( data, width, height, channels, &DDS_size ); - } - /* save it */ - memset( &header, 0, sizeof( DDS_header ) ); - header.dwMagic = ('D' << 0) | ('D' << 8) | ('S' << 16) | (' ' << 24); - header.dwSize = 124; - header.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | DDSD_LINEARSIZE; - header.dwWidth = width; - header.dwHeight = height; - header.dwPitchOrLinearSize = DDS_size; - header.sPixelFormat.dwSize = 32; - header.sPixelFormat.dwFlags = DDPF_FOURCC; - if( (channels & 1) == 1 ) - { - header.sPixelFormat.dwFourCC = ('D' << 0) | ('X' << 8) | ('T' << 16) | ('1' << 24); - } else - { - header.sPixelFormat.dwFourCC = ('D' << 0) | ('X' << 8) | ('T' << 16) | ('5' << 24); - } - header.sCaps.dwCaps1 = DDSCAPS_TEXTURE; - /* write it out */ - fout = fopen( filename, "wb"); - fwrite( &header, sizeof( DDS_header ), 1, fout ); - fwrite( DDS_data, 1, DDS_size, fout ); - fclose( fout ); - /* done */ - free( DDS_data ); - return 1; -} - -unsigned char* convert_image_to_DXT1( - const unsigned char *const uncompressed, - int width, int height, int channels, - int *out_size ) -{ - unsigned char *compressed; - int i, j, x, y; - unsigned char ublock[16*3]; - unsigned char cblock[8]; - int index = 0, chan_step = 1; - int block_count = 0; - /* error check */ - *out_size = 0; - if( (width < 1) || (height < 1) || - (NULL == uncompressed) || - (channels < 1) || (channels > 4) ) - { - return NULL; - } - /* for channels == 1 or 2, I do not step forward for R,G,B values */ - if( channels < 3 ) - { - chan_step = 0; - } - /* get the RAM for the compressed image - (8 bytes per 4x4 pixel block) */ - *out_size = ((width+3) >> 2) * ((height+3) >> 2) * 8; - compressed = (unsigned char*)malloc( *out_size ); - /* go through each block */ - for( j = 0; j < height; j += 4 ) - { - for( i = 0; i < width; i += 4 ) - { - /* copy this block into a new one */ - int idx = 0; - int mx = 4, my = 4; - if( j+4 >= height ) - { - my = height - j; - } - if( i+4 >= width ) - { - mx = width - i; - } - for( y = 0; y < my; ++y ) - { - for( x = 0; x < mx; ++x ) - { - ublock[idx++] = uncompressed[(j+y)*width*channels+(i+x)*channels]; - ublock[idx++] = uncompressed[(j+y)*width*channels+(i+x)*channels+chan_step]; - ublock[idx++] = uncompressed[(j+y)*width*channels+(i+x)*channels+chan_step+chan_step]; - } - for( x = mx; x < 4; ++x ) - { - ublock[idx++] = ublock[0]; - ublock[idx++] = ublock[1]; - ublock[idx++] = ublock[2]; - } - } - for( y = my; y < 4; ++y ) - { - for( x = 0; x < 4; ++x ) - { - ublock[idx++] = ublock[0]; - ublock[idx++] = ublock[1]; - ublock[idx++] = ublock[2]; - } - } - /* compress the block */ - ++block_count; - compress_DDS_color_block( 3, ublock, cblock ); - /* copy the data from the block into the main block */ - for( x = 0; x < 8; ++x ) - { - compressed[index++] = cblock[x]; - } - } - } - return compressed; -} - -unsigned char* convert_image_to_DXT5( - const unsigned char *const uncompressed, - int width, int height, int channels, - int *out_size ) -{ - unsigned char *compressed; - int i, j, x, y; - unsigned char ublock[16*4]; - unsigned char cblock[8]; - int index = 0, chan_step = 1; - int block_count = 0, has_alpha; - /* error check */ - *out_size = 0; - if( (width < 1) || (height < 1) || - (NULL == uncompressed) || - (channels < 1) || ( channels > 4) ) - { - return NULL; - } - /* for channels == 1 or 2, I do not step forward for R,G,B vales */ - if( channels < 3 ) - { - chan_step = 0; - } - /* # channels = 1 or 3 have no alpha, 2 & 4 do have alpha */ - has_alpha = 1 - (channels & 1); - /* get the RAM for the compressed image - (16 bytes per 4x4 pixel block) */ - *out_size = ((width+3) >> 2) * ((height+3) >> 2) * 16; - compressed = (unsigned char*)malloc( *out_size ); - /* go through each block */ - for( j = 0; j < height; j += 4 ) - { - for( i = 0; i < width; i += 4 ) - { - /* local variables, and my block counter */ - int idx = 0; - int mx = 4, my = 4; - if( j+4 >= height ) - { - my = height - j; - } - if( i+4 >= width ) - { - mx = width - i; - } - for( y = 0; y < my; ++y ) - { - for( x = 0; x < mx; ++x ) - { - ublock[idx++] = uncompressed[(j+y)*width*channels+(i+x)*channels]; - ublock[idx++] = uncompressed[(j+y)*width*channels+(i+x)*channels+chan_step]; - ublock[idx++] = uncompressed[(j+y)*width*channels+(i+x)*channels+chan_step+chan_step]; - ublock[idx++] = - has_alpha * uncompressed[(j+y)*width*channels+(i+x)*channels+channels-1] - + (1-has_alpha)*255; - } - for( x = mx; x < 4; ++x ) - { - ublock[idx++] = ublock[0]; - ublock[idx++] = ublock[1]; - ublock[idx++] = ublock[2]; - ublock[idx++] = ublock[3]; - } - } - for( y = my; y < 4; ++y ) - { - for( x = 0; x < 4; ++x ) - { - ublock[idx++] = ublock[0]; - ublock[idx++] = ublock[1]; - ublock[idx++] = ublock[2]; - ublock[idx++] = ublock[3]; - } - } - /* now compress the alpha block */ - compress_DDS_alpha_block( ublock, cblock ); - /* copy the data from the compressed alpha block into the main buffer */ - for( x = 0; x < 8; ++x ) - { - compressed[index++] = cblock[x]; - } - /* then compress the color block */ - ++block_count; - compress_DDS_color_block( 4, ublock, cblock ); - /* copy the data from the compressed color block into the main buffer */ - for( x = 0; x < 8; ++x ) - { - compressed[index++] = cblock[x]; - } - } - } - return compressed; -} - -/********* Helper Functions *********/ -int convert_bit_range( int c, int from_bits, int to_bits ) -{ - int b = (1 << (from_bits - 1)) + c * ((1 << to_bits) - 1); - return (b + (b >> from_bits)) >> from_bits; -} - -int rgb_to_565( int r, int g, int b ) -{ - return - (convert_bit_range( r, 8, 5 ) << 11) | - (convert_bit_range( g, 8, 6 ) << 05) | - (convert_bit_range( b, 8, 5 ) << 00); -} - -void rgb_888_from_565( unsigned int c, int *r, int *g, int *b ) -{ - *r = convert_bit_range( (c >> 11) & 31, 5, 8 ); - *g = convert_bit_range( (c >> 05) & 63, 6, 8 ); - *b = convert_bit_range( (c >> 00) & 31, 5, 8 ); -} - -void compute_color_line_STDEV( - const unsigned char *const uncompressed, - int channels, - float point[3], float direction[3] ) -{ - const float inv_16 = 1.0f / 16.0f; - int i; - float sum_r = 0.0f, sum_g = 0.0f, sum_b = 0.0f; - float sum_rr = 0.0f, sum_gg = 0.0f, sum_bb = 0.0f; - float sum_rg = 0.0f, sum_rb = 0.0f, sum_gb = 0.0f; - /* calculate all data needed for the covariance matrix - ( to compare with _rygdxt code) */ - for( i = 0; i < 16*channels; i += channels ) - { - sum_r += uncompressed[i+0]; - sum_rr += uncompressed[i+0] * uncompressed[i+0]; - sum_g += uncompressed[i+1]; - sum_gg += uncompressed[i+1] * uncompressed[i+1]; - sum_b += uncompressed[i+2]; - sum_bb += uncompressed[i+2] * uncompressed[i+2]; - sum_rg += uncompressed[i+0] * uncompressed[i+1]; - sum_rb += uncompressed[i+0] * uncompressed[i+2]; - sum_gb += uncompressed[i+1] * uncompressed[i+2]; - } - /* convert the sums to averages */ - sum_r *= inv_16; - sum_g *= inv_16; - sum_b *= inv_16; - /* and convert the squares to the squares of the value - avg_value */ - sum_rr -= 16.0f * sum_r * sum_r; - sum_gg -= 16.0f * sum_g * sum_g; - sum_bb -= 16.0f * sum_b * sum_b; - sum_rg -= 16.0f * sum_r * sum_g; - sum_rb -= 16.0f * sum_r * sum_b; - sum_gb -= 16.0f * sum_g * sum_b; - /* the point on the color line is the average */ - point[0] = sum_r; - point[1] = sum_g; - point[2] = sum_b; - #if USE_COV_MAT - /* - The following idea was from ryg. - (https://mollyrocket.com/forums/viewtopic.php?t=392) - The method worked great (less RMSE than mine) most of - the time, but had some issues handling some simple - boundary cases, like full green next to full red, - which would generate a covariance matrix like this: - - | 1 -1 0 | - | -1 1 0 | - | 0 0 0 | - - For a given starting vector, the power method can - generate all zeros! So no starting with {1,1,1} - as I was doing! This kind of error is still a - slight posibillity, but will be very rare. - */ - /* use the covariance matrix directly - (1st iteration, don't use all 1.0 values!) */ - sum_r = 1.0f; - sum_g = 2.718281828f; - sum_b = 3.141592654f; - direction[0] = sum_r*sum_rr + sum_g*sum_rg + sum_b*sum_rb; - direction[1] = sum_r*sum_rg + sum_g*sum_gg + sum_b*sum_gb; - direction[2] = sum_r*sum_rb + sum_g*sum_gb + sum_b*sum_bb; - /* 2nd iteration, use results from the 1st guy */ - sum_r = direction[0]; - sum_g = direction[1]; - sum_b = direction[2]; - direction[0] = sum_r*sum_rr + sum_g*sum_rg + sum_b*sum_rb; - direction[1] = sum_r*sum_rg + sum_g*sum_gg + sum_b*sum_gb; - direction[2] = sum_r*sum_rb + sum_g*sum_gb + sum_b*sum_bb; - /* 3rd iteration, use results from the 2nd guy */ - sum_r = direction[0]; - sum_g = direction[1]; - sum_b = direction[2]; - direction[0] = sum_r*sum_rr + sum_g*sum_rg + sum_b*sum_rb; - direction[1] = sum_r*sum_rg + sum_g*sum_gg + sum_b*sum_gb; - direction[2] = sum_r*sum_rb + sum_g*sum_gb + sum_b*sum_bb; - #else - /* use my standard deviation method - (very robust, a tiny bit slower and less accurate) */ - direction[0] = sqrt( sum_rr ); - direction[1] = sqrt( sum_gg ); - direction[2] = sqrt( sum_bb ); - /* which has a greater component */ - if( sum_gg > sum_rr ) - { - /* green has greater component, so base the other signs off of green */ - if( sum_rg < 0.0f ) - { - direction[0] = -direction[0]; - } - if( sum_gb < 0.0f ) - { - direction[2] = -direction[2]; - } - } else - { - /* red has a greater component */ - if( sum_rg < 0.0f ) - { - direction[1] = -direction[1]; - } - if( sum_rb < 0.0f ) - { - direction[2] = -direction[2]; - } - } - #endif -} - -void LSE_master_colors_max_min( - int *cmax, int *cmin, - int channels, - const unsigned char *const uncompressed ) -{ - int i, j; - /* the master colors */ - int c0[3], c1[3]; - /* used for fitting the line */ - float sum_x[] = { 0.0f, 0.0f, 0.0f }; - float sum_x2[] = { 0.0f, 0.0f, 0.0f }; - float dot_max = 1.0f, dot_min = -1.0f; - float vec_len2 = 0.0f; - float dot; - /* error check */ - if( (channels < 3) || (channels > 4) ) - { - return; - } - compute_color_line_STDEV( uncompressed, channels, sum_x, sum_x2 ); - vec_len2 = 1.0f / ( 0.00001f + - sum_x2[0]*sum_x2[0] + sum_x2[1]*sum_x2[1] + sum_x2[2]*sum_x2[2] ); - /* finding the max and min vector values */ - dot_max = - ( - sum_x2[0] * uncompressed[0] + - sum_x2[1] * uncompressed[1] + - sum_x2[2] * uncompressed[2] - ); - dot_min = dot_max; - for( i = 1; i < 16; ++i ) - { - dot = - ( - sum_x2[0] * uncompressed[i*channels+0] + - sum_x2[1] * uncompressed[i*channels+1] + - sum_x2[2] * uncompressed[i*channels+2] - ); - if( dot < dot_min ) - { - dot_min = dot; - } else if( dot > dot_max ) - { - dot_max = dot; - } - } - /* and the offset (from the average location) */ - dot = sum_x2[0]*sum_x[0] + sum_x2[1]*sum_x[1] + sum_x2[2]*sum_x[2]; - dot_min -= dot; - dot_max -= dot; - /* post multiply by the scaling factor */ - dot_min *= vec_len2; - dot_max *= vec_len2; - /* OK, build the master colors */ - for( i = 0; i < 3; ++i ) - { - /* color 0 */ - c0[i] = (int)(0.5f + sum_x[i] + dot_max * sum_x2[i]); - if( c0[i] < 0 ) - { - c0[i] = 0; - } else if( c0[i] > 255 ) - { - c0[i] = 255; - } - /* color 1 */ - c1[i] = (int)(0.5f + sum_x[i] + dot_min * sum_x2[i]); - if( c1[i] < 0 ) - { - c1[i] = 0; - } else if( c1[i] > 255 ) - { - c1[i] = 255; - } - } - /* down_sample (with rounding?) */ - i = rgb_to_565( c0[0], c0[1], c0[2] ); - j = rgb_to_565( c1[0], c1[1], c1[2] ); - if( i > j ) - { - *cmax = i; - *cmin = j; - } else - { - *cmax = j; - *cmin = i; - } -} - -void - compress_DDS_color_block - ( - int channels, - const unsigned char *const uncompressed, - unsigned char compressed[8] - ) -{ - /* variables */ - int i; - int next_bit; - int enc_c0, enc_c1; - int c0[4], c1[4]; - float color_line[] = { 0.0f, 0.0f, 0.0f, 0.0f }; - float vec_len2 = 0.0f, dot_offset = 0.0f; - /* stupid order */ - int swizzle4[] = { 0, 2, 3, 1 }; - /* get the master colors */ - LSE_master_colors_max_min( &enc_c0, &enc_c1, channels, uncompressed ); - /* store the 565 color 0 and color 1 */ - compressed[0] = (enc_c0 >> 0) & 255; - compressed[1] = (enc_c0 >> 8) & 255; - compressed[2] = (enc_c1 >> 0) & 255; - compressed[3] = (enc_c1 >> 8) & 255; - /* zero out the compressed data */ - compressed[4] = 0; - compressed[5] = 0; - compressed[6] = 0; - compressed[7] = 0; - /* reconstitute the master color vectors */ - rgb_888_from_565( enc_c0, &c0[0], &c0[1], &c0[2] ); - rgb_888_from_565( enc_c1, &c1[0], &c1[1], &c1[2] ); - /* the new vector */ - vec_len2 = 0.0f; - for( i = 0; i < 3; ++i ) - { - color_line[i] = (float)(c1[i] - c0[i]); - vec_len2 += color_line[i] * color_line[i]; - } - if( vec_len2 > 0.0f ) - { - vec_len2 = 1.0f / vec_len2; - } - /* pre-proform the scaling */ - color_line[0] *= vec_len2; - color_line[1] *= vec_len2; - color_line[2] *= vec_len2; - /* compute the offset (constant) portion of the dot product */ - dot_offset = color_line[0]*c0[0] + color_line[1]*c0[1] + color_line[2]*c0[2]; - /* store the rest of the bits */ - next_bit = 8*4; - for( i = 0; i < 16; ++i ) - { - /* find the dot product of this color, to place it on the line - (should be [-1,1]) */ - int next_value = 0; - float dot_product = - color_line[0] * uncompressed[i*channels+0] + - color_line[1] * uncompressed[i*channels+1] + - color_line[2] * uncompressed[i*channels+2] - - dot_offset; - /* map to [0,3] */ - next_value = (int)( dot_product * 3.0f + 0.5f ); - if( next_value > 3 ) - { - next_value = 3; - } else if( next_value < 0 ) - { - next_value = 0; - } - /* OK, store this value */ - compressed[next_bit >> 3] |= swizzle4[ next_value ] << (next_bit & 7); - next_bit += 2; - } - /* done compressing to DXT1 */ -} - -void - compress_DDS_alpha_block - ( - const unsigned char *const uncompressed, - unsigned char compressed[8] - ) -{ - /* variables */ - int i; - int next_bit; - int a0, a1; - float scale_me; - /* stupid order */ - int swizzle8[] = { 1, 7, 6, 5, 4, 3, 2, 0 }; - /* get the alpha limits (a0 > a1) */ - a0 = a1 = uncompressed[3]; - for( i = 4+3; i < 16*4; i += 4 ) - { - if( uncompressed[i] > a0 ) - { - a0 = uncompressed[i]; - } else if( uncompressed[i] < a1 ) - { - a1 = uncompressed[i]; - } - } - /* store those limits, and zero the rest of the compressed dataset */ - compressed[0] = a0; - compressed[1] = a1; - /* zero out the compressed data */ - compressed[2] = 0; - compressed[3] = 0; - compressed[4] = 0; - compressed[5] = 0; - compressed[6] = 0; - compressed[7] = 0; - /* store the all of the alpha values */ - next_bit = 8*2; - scale_me = 7.9999f / (a0 - a1); - for( i = 3; i < 16*4; i += 4 ) - { - /* convert this alpha value to a 3 bit number */ - int svalue; - int value = (int)((uncompressed[i] - a1) * scale_me); - svalue = swizzle8[ value&7 ]; - /* OK, store this value, start with the 1st byte */ - compressed[next_bit >> 3] |= svalue << (next_bit & 7); - if( (next_bit & 7) > 5 ) - { - /* spans 2 bytes, fill in the start of the 2nd byte */ - compressed[1 + (next_bit >> 3)] |= svalue >> (8 - (next_bit & 7) ); - } - next_bit += 3; - } - /* done compressing to DXT1 */ -} diff --git a/lib/SOIL2/image_DXT.h b/lib/SOIL2/image_DXT.h deleted file mode 100644 index 75f604f42..000000000 --- a/lib/SOIL2/image_DXT.h +++ /dev/null @@ -1,123 +0,0 @@ -/* - Jonathan Dummer - 2007-07-31-10.32 - - simple DXT compression / decompression code - - public domain -*/ - -#ifndef HEADER_IMAGE_DXT -#define HEADER_IMAGE_DXT - -/** - Converts an image from an array of unsigned chars (RGB or RGBA) to - DXT1 or DXT5, then saves the converted image to disk. - \return 0 if failed, otherwise returns 1 -**/ -int -save_image_as_DDS -( - const char *filename, - int width, int height, int channels, - const unsigned char *const data -); - -/** - take an image and convert it to DXT1 (no alpha) -**/ -unsigned char* -convert_image_to_DXT1 -( - const unsigned char *const uncompressed, - int width, int height, int channels, - int *out_size -); - -/** - take an image and convert it to DXT5 (with alpha) -**/ -unsigned char* -convert_image_to_DXT5 -( - const unsigned char *const uncompressed, - int width, int height, int channels, - int *out_size -); - -/** A bunch of DirectDraw Surface structures and flags **/ -typedef struct -{ - unsigned int dwMagic; - unsigned int dwSize; - unsigned int dwFlags; - unsigned int dwHeight; - unsigned int dwWidth; - unsigned int dwPitchOrLinearSize; - unsigned int dwDepth; - unsigned int dwMipMapCount; - unsigned int dwReserved1[ 11 ]; - - /* DDPIXELFORMAT */ - struct - { - unsigned int dwSize; - unsigned int dwFlags; - unsigned int dwFourCC; - unsigned int dwRGBBitCount; - unsigned int dwRBitMask; - unsigned int dwGBitMask; - unsigned int dwBBitMask; - unsigned int dwAlphaBitMask; - } - sPixelFormat; - - /* DDCAPS2 */ - struct - { - unsigned int dwCaps1; - unsigned int dwCaps2; - unsigned int dwDDSX; - unsigned int dwReserved; - } - sCaps; - unsigned int dwReserved2; -} -DDS_header ; - -/* the following constants were copied directly off the MSDN website */ - -/* The dwFlags member of the original DDSURFACEDESC2 structure - can be set to one or more of the following values. */ -#define DDSD_CAPS 0x00000001 -#define DDSD_HEIGHT 0x00000002 -#define DDSD_WIDTH 0x00000004 -#define DDSD_PITCH 0x00000008 -#define DDSD_PIXELFORMAT 0x00001000 -#define DDSD_MIPMAPCOUNT 0x00020000 -#define DDSD_LINEARSIZE 0x00080000 -#define DDSD_DEPTH 0x00800000 - -/* DirectDraw Pixel Format */ -#define DDPF_ALPHAPIXELS 0x00000001 -#define DDPF_FOURCC 0x00000004 -#define DDPF_RGB 0x00000040 - -/* The dwCaps1 member of the DDSCAPS2 structure can be - set to one or more of the following values. */ -#define DDSCAPS_COMPLEX 0x00000008 -#define DDSCAPS_TEXTURE 0x00001000 -#define DDSCAPS_MIPMAP 0x00400000 - -/* The dwCaps2 member of the DDSCAPS2 structure can be - set to one or more of the following values. */ -#define DDSCAPS2_CUBEMAP 0x00000200 -#define DDSCAPS2_CUBEMAP_POSITIVEX 0x00000400 -#define DDSCAPS2_CUBEMAP_NEGATIVEX 0x00000800 -#define DDSCAPS2_CUBEMAP_POSITIVEY 0x00001000 -#define DDSCAPS2_CUBEMAP_NEGATIVEY 0x00002000 -#define DDSCAPS2_CUBEMAP_POSITIVEZ 0x00004000 -#define DDSCAPS2_CUBEMAP_NEGATIVEZ 0x00008000 -#define DDSCAPS2_VOLUME 0x00200000 - -#endif /* HEADER_IMAGE_DXT */ diff --git a/lib/SOIL2/image_helper.c b/lib/SOIL2/image_helper.c deleted file mode 100644 index 12c701ee6..000000000 --- a/lib/SOIL2/image_helper.c +++ /dev/null @@ -1,435 +0,0 @@ -/* - Jonathan Dummer - - image helper functions - - MIT license -*/ - -#include "image_helper.h" -#include -#include - -/* Upscaling the image uses simple bilinear interpolation */ -int - up_scale_image - ( - const unsigned char* const orig, - int width, int height, int channels, - unsigned char* resampled, - int resampled_width, int resampled_height - ) -{ - float dx, dy; - int x, y, c; - - /* error(s) check */ - if ( (width < 1) || (height < 1) || - (resampled_width < 2) || (resampled_height < 2) || - (channels < 1) || - (NULL == orig) || (NULL == resampled) ) - { - /* signify badness */ - return 0; - } - /* - for each given pixel in the new map, find the exact location - from the original map which would contribute to this guy - */ - dx = (width - 1.0f) / (resampled_width - 1.0f); - dy = (height - 1.0f) / (resampled_height - 1.0f); - for ( y = 0; y < resampled_height; ++y ) - { - /* find the base y index and fractional offset from that */ - float sampley = y * dy; - int inty = (int)sampley; - /* if( inty < 0 ) { inty = 0; } else */ - if( inty > height - 2 ) { inty = height - 2; } - sampley -= inty; - for ( x = 0; x < resampled_width; ++x ) - { - float samplex = x * dx; - int intx = (int)samplex; - int base_index; - /* find the base x index and fractional offset from that */ - /* if( intx < 0 ) { intx = 0; } else */ - if( intx > width - 2 ) { intx = width - 2; } - samplex -= intx; - /* base index into the original image */ - base_index = (inty * width + intx) * channels; - for ( c = 0; c < channels; ++c ) - { - /* do the sampling */ - float value = 0.5f; - value += orig[base_index] - *(1.0f-samplex)*(1.0f-sampley); - value += orig[base_index+channels] - *(samplex)*(1.0f-sampley); - value += orig[base_index+width*channels] - *(1.0f-samplex)*(sampley); - value += orig[base_index+width*channels+channels] - *(samplex)*(sampley); - /* move to the next channel */ - ++base_index; - /* save the new value */ - resampled[y*resampled_width*channels+x*channels+c] = - (unsigned char)(value); - } - } - } - /* done */ - return 1; -} - -int - mipmap_image - ( - const unsigned char* const orig, - int width, int height, int channels, - unsigned char* resampled, - int block_size_x, int block_size_y - ) -{ - int mip_width, mip_height; - int i, j, c; - - /* error check */ - if( (width < 1) || (height < 1) || - (channels < 1) || (orig == NULL) || - (resampled == NULL) || - (block_size_x < 1) || (block_size_y < 1) ) - { - /* nothing to do */ - return 0; - } - mip_width = width / block_size_x; - mip_height = height / block_size_y; - if( mip_width < 1 ) - { - mip_width = 1; - } - if( mip_height < 1 ) - { - mip_height = 1; - } - for( j = 0; j < mip_height; ++j ) - { - for( i = 0; i < mip_width; ++i ) - { - for( c = 0; c < channels; ++c ) - { - const int index = (j*block_size_y)*width*channels + (i*block_size_x)*channels + c; - int sum_value; - int u,v; - int u_block = block_size_x; - int v_block = block_size_y; - int block_area; - /* do a bit of checking so we don't over-run the boundaries - (necessary for non-square textures!) */ - if( block_size_x * (i+1) > width ) - { - u_block = width - i*block_size_y; - } - if( block_size_y * (j+1) > height ) - { - v_block = height - j*block_size_y; - } - block_area = u_block*v_block; - /* for this pixel, see what the average - of all the values in the block are. - note: start the sum at the rounding value, not at 0 */ - sum_value = block_area >> 1; - for( v = 0; v < v_block; ++v ) - for( u = 0; u < u_block; ++u ) - { - sum_value += orig[index + v*width*channels + u*channels]; - } - resampled[j*mip_width*channels + i*channels + c] = sum_value / block_area; - } - } - } - return 1; -} - -int - scale_image_RGB_to_NTSC_safe - ( - unsigned char* orig, - int width, int height, int channels - ) -{ - const float scale_lo = 16.0f - 0.499f; - const float scale_hi = 235.0f + 0.499f; - int i, j; - int nc = channels; - unsigned char scale_LUT[256]; - /* error check */ - if( (width < 1) || (height < 1) || - (channels < 1) || (orig == NULL) ) - { - /* nothing to do */ - return 0; - } - /* set up the scaling Look Up Table */ - for( i = 0; i < 256; ++i ) - { - scale_LUT[i] = (unsigned char)((scale_hi - scale_lo) * i / 255.0f + scale_lo); - } - /* for channels = 2 or 4, ignore the alpha component */ - nc -= 1 - (channels & 1); - /* OK, go through the image and scale any non-alpha components */ - for( i = 0; i < width*height*channels; i += channels ) - { - for( j = 0; j < nc; ++j ) - { - orig[i+j] = scale_LUT[orig[i+j]]; - } - } - return 1; -} - -unsigned char clamp_byte( int x ) { return ( (x) < 0 ? (0) : ( (x) > 255 ? 255 : (x) ) ); } - -/* - This function takes the RGB components of the image - and converts them into YCoCg. 3 components will be - re-ordered to CoYCg (for optimum DXT1 compression), - while 4 components will be ordered CoCgAY (for DXT5 - compression). -*/ -int - convert_RGB_to_YCoCg - ( - unsigned char* orig, - int width, int height, int channels - ) -{ - int i; - /* error check */ - if( (width < 1) || (height < 1) || - (channels < 3) || (channels > 4) || - (orig == NULL) ) - { - /* nothing to do */ - return -1; - } - /* do the conversion */ - if( channels == 3 ) - { - for( i = 0; i < width*height*3; i += 3 ) - { - int r = orig[i+0]; - int g = (orig[i+1] + 1) >> 1; - int b = orig[i+2]; - int tmp = (2 + r + b) >> 2; - /* Co */ - orig[i+0] = clamp_byte( 128 + ((r - b + 1) >> 1) ); - /* Y */ - orig[i+1] = clamp_byte( g + tmp ); - /* Cg */ - orig[i+2] = clamp_byte( 128 + g - tmp ); - } - } else - { - for( i = 0; i < width*height*4; i += 4 ) - { - int r = orig[i+0]; - int g = (orig[i+1] + 1) >> 1; - int b = orig[i+2]; - unsigned char a = orig[i+3]; - int tmp = (2 + r + b) >> 2; - /* Co */ - orig[i+0] = clamp_byte( 128 + ((r - b + 1) >> 1) ); - /* Cg */ - orig[i+1] = clamp_byte( 128 + g - tmp ); - /* Alpha */ - orig[i+2] = a; - /* Y */ - orig[i+3] = clamp_byte( g + tmp ); - } - } - /* done */ - return 0; -} - -/* - This function takes the YCoCg components of the image - and converts them into RGB. See above. -*/ -int - convert_YCoCg_to_RGB - ( - unsigned char* orig, - int width, int height, int channels - ) -{ - int i; - /* error check */ - if( (width < 1) || (height < 1) || - (channels < 3) || (channels > 4) || - (orig == NULL) ) - { - /* nothing to do */ - return -1; - } - /* do the conversion */ - if( channels == 3 ) - { - for( i = 0; i < width*height*3; i += 3 ) - { - int co = orig[i+0] - 128; - int y = orig[i+1]; - int cg = orig[i+2] - 128; - /* R */ - orig[i+0] = clamp_byte( y + co - cg ); - /* G */ - orig[i+1] = clamp_byte( y + cg ); - /* B */ - orig[i+2] = clamp_byte( y - co - cg ); - } - } else - { - for( i = 0; i < width*height*4; i += 4 ) - { - int co = orig[i+0] - 128; - int cg = orig[i+1] - 128; - unsigned char a = orig[i+2]; - int y = orig[i+3]; - /* R */ - orig[i+0] = clamp_byte( y + co - cg ); - /* G */ - orig[i+1] = clamp_byte( y + cg ); - /* B */ - orig[i+2] = clamp_byte( y - co - cg ); - /* A */ - orig[i+3] = a; - } - } - /* done */ - return 0; -} - -float -find_max_RGBE -( - unsigned char *image, - int width, int height -) -{ - float max_val = 0.0f; - unsigned char *img = image; - int i, j; - for( i = width * height; i > 0; --i ) - { - /* float scale = powf( 2.0f, img[3] - 128.0f ) / 255.0f; */ - float scale = (float)ldexp( 1.0f / 255.0f, (int)(img[3]) - 128 ); - for( j = 0; j < 3; ++j ) - { - if( img[j] * scale > max_val ) - { - max_val = img[j] * scale; - } - } - /* next pixel */ - img += 4; - } - return max_val; -} - -int -RGBE_to_RGBdivA -( - unsigned char *image, - int width, int height, - int rescale_to_max -) -{ - /* local variables */ - int i, iv; - unsigned char *img = image; - float scale = 1.0f; - /* error check */ - if( (!image) || (width < 1) || (height < 1) ) - { - return 0; - } - /* convert (note: no negative numbers, but 0.0 is possible) */ - if( rescale_to_max ) - { - scale = 255.0f / find_max_RGBE( image, width, height ); - } - for( i = width * height; i > 0; --i ) - { - /* decode this pixel, and find the max */ - float r,g,b,e, m; - /* e = scale * powf( 2.0f, img[3] - 128.0f ) / 255.0f; */ - e = scale * (float)ldexp( 1.0f / 255.0f, (int)(img[3]) - 128 ); - r = e * img[0]; - g = e * img[1]; - b = e * img[2]; - m = (r > g) ? r : g; - m = (b > m) ? b : m; - /* and encode it into RGBdivA */ - iv = (m != 0.0f) ? (int)(255.0f / m) : 1; - iv = (iv < 1) ? 1 : iv; - img[3] = (iv > 255) ? 255 : iv; - iv = (int)(img[3] * r + 0.5f); - img[0] = (iv > 255) ? 255 : iv; - iv = (int)(img[3] * g + 0.5f); - img[1] = (iv > 255) ? 255 : iv; - iv = (int)(img[3] * b + 0.5f); - img[2] = (iv > 255) ? 255 : iv; - /* and on to the next pixel */ - img += 4; - } - return 1; -} - -int -RGBE_to_RGBdivA2 -( - unsigned char *image, - int width, int height, - int rescale_to_max -) -{ - /* local variables */ - int i, iv; - unsigned char *img = image; - float scale = 1.0f; - /* error check */ - if( (!image) || (width < 1) || (height < 1) ) - { - return 0; - } - /* convert (note: no negative numbers, but 0.0 is possible) */ - if( rescale_to_max ) - { - scale = 255.0f * 255.0f / find_max_RGBE( image, width, height ); - } - for( i = width * height; i > 0; --i ) - { - /* decode this pixel, and find the max */ - float r,g,b,e, m; - /* e = scale * powf( 2.0f, img[3] - 128.0f ) / 255.0f; */ - e = scale * (float)ldexp( 1.0f / 255.0f, (int)(img[3]) - 128 ); - r = e * img[0]; - g = e * img[1]; - b = e * img[2]; - m = (r > g) ? r : g; - m = (b > m) ? b : m; - /* and encode it into RGBdivA */ - iv = (m != 0.0f) ? (int)sqrtf( 255.0f * 255.0f / m ) : 1; - iv = (iv < 1) ? 1 : iv; - img[3] = (iv > 255) ? 255 : iv; - iv = (int)(img[3] * img[3] * r / 255.0f + 0.5f); - img[0] = (iv > 255) ? 255 : iv; - iv = (int)(img[3] * img[3] * g / 255.0f + 0.5f); - img[1] = (iv > 255) ? 255 : iv; - iv = (int)(img[3] * img[3] * b / 255.0f + 0.5f); - img[2] = (iv > 255) ? 255 : iv; - /* and on to the next pixel */ - img += 4; - } - return 1; -} diff --git a/lib/SOIL2/image_helper.h b/lib/SOIL2/image_helper.h deleted file mode 100644 index 3fa2662f0..000000000 --- a/lib/SOIL2/image_helper.h +++ /dev/null @@ -1,115 +0,0 @@ -/* - Jonathan Dummer - - Image helper functions - - MIT license -*/ - -#ifndef HEADER_IMAGE_HELPER -#define HEADER_IMAGE_HELPER - -#ifdef __cplusplus -extern "C" { -#endif - -/** - This function upscales an image. - Not to be used to create MIPmaps, - but to make it square, - or to make it a power-of-two sized. -**/ -int - up_scale_image - ( - const unsigned char* const orig, - int width, int height, int channels, - unsigned char* resampled, - int resampled_width, int resampled_height - ); - -/** - This function downscales an image. - Used for creating MIPmaps, - the incoming image should be a - power-of-two sized. -**/ -int - mipmap_image - ( - const unsigned char* const orig, - int width, int height, int channels, - unsigned char* resampled, - int block_size_x, int block_size_y - ); - -/** - This function takes the RGB components of the image - and scales each channel from [0,255] to [16,235]. - This makes the colors "Safe" for display on NTSC - displays. Note that this is _NOT_ a good idea for - loading images like normal- or height-maps! -**/ -int - scale_image_RGB_to_NTSC_safe - ( - unsigned char* orig, - int width, int height, int channels - ); - -/** - This function takes the RGB components of the image - and converts them into YCoCg. 3 components will be - re-ordered to CoYCg (for optimum DXT1 compression), - while 4 components will be ordered CoCgAY (for DXT5 - compression). -**/ -int - convert_RGB_to_YCoCg - ( - unsigned char* orig, - int width, int height, int channels - ); - -/** - This function takes the YCoCg components of the image - and converts them into RGB. See above. -**/ -int - convert_YCoCg_to_RGB - ( - unsigned char* orig, - int width, int height, int channels - ); - -/** - Converts an HDR image from an array - of unsigned chars (RGBE) to RGBdivA - \return 0 if failed, otherwise returns 1 -**/ -int - RGBE_to_RGBdivA - ( - unsigned char *image, - int width, int height, - int rescale_to_max - ); - -/** - Converts an HDR image from an array - of unsigned chars (RGBE) to RGBdivA2 - \return 0 if failed, otherwise returns 1 -**/ -int - RGBE_to_RGBdivA2 - ( - unsigned char *image, - int width, int height, - int rescale_to_max - ); - -#ifdef __cplusplus -} -#endif - -#endif /* HEADER_IMAGE_HELPER */ diff --git a/lib/SOIL2/jo_jpeg.h b/lib/SOIL2/jo_jpeg.h deleted file mode 100644 index 353300b03..000000000 --- a/lib/SOIL2/jo_jpeg.h +++ /dev/null @@ -1,340 +0,0 @@ -/* public domain Simple, Minimalistic JPEG writer - http://jonolick.com - * - * Quick Notes: - * Based on a javascript jpeg writer - * JPEG baseline (no JPEG progressive) - * Supports 1, 3 or 4 component input. (luminance, RGB or RGBX) - * - * Latest revisions: - * 1.53 (2016-07-08) Added support to compile as plain C code. - * 1.52 (2012-22-11) Added support for specifying Luminance, RGB, or RGBA via comp(onents) argument (1, 3 and 4 respectively). - * 1.51 (2012-19-11) Fixed some warnings - * 1.50 (2012-18-11) MT safe. Simplified. Optimized. Reduced memory requirements. Zero allocations. No namespace polution. Approx 340 lines code. - * 1.10 (2012-16-11) compile fixes, added docs, - * changed from .h to .cpp (simpler to bootstrap), etc - * 1.00 (2012-02-02) initial release - * - * Basic usage: - * char *foo = new char[128*128*4]; // 4 component. RGBX format, where X is unused - * jo_write_jpg("foo.jpg", foo, 128, 128, 4, 90); // comp can be 1, 3, or 4. Lum, RGB, or RGBX respectively. - * - * */ - -#ifndef JO_INCLUDE_JPEG_H -#define JO_INCLUDE_JPEG_H - -// To get a header file for this, either cut and paste the header, -// or create jo_jpeg.h, #define JO_JPEG_HEADER_FILE_ONLY, and -// then include jo_jpeg.c from it. - -// Returns false on failure -extern int jo_write_jpg(const char *filename, const void *data, int width, int height, int comp, int quality); - -#endif // JO_INCLUDE_JPEG_H - -#ifndef JO_JPEG_HEADER_FILE_ONLY - -#if defined(_MSC_VER) && _MSC_VER >= 0x1400 -#define _CRT_SECURE_NO_WARNINGS // suppress warnings about fopen() -#endif - -#include -#include -#include - -static const unsigned char s_jo_ZigZag[] = { 0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18,24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63 }; - -static void jo_writeBits(FILE *fp, int *bitBuf, int *bitCnt, const unsigned short *bs) { - *bitCnt += bs[1]; - *bitBuf |= bs[0] << (24 - *bitCnt); - while(*bitCnt >= 8) { - unsigned char c = (*bitBuf >> 16) & 255; - putc(c, fp); - if(c == 255) { - putc(0, fp); - } - *bitBuf <<= 8; - *bitCnt -= 8; - } -} - -static void jo_DCT(float *d0, float *d1, float *d2, float *d3, float *d4, float *d5, float *d6, float *d7) { - float tmp0 = *d0 + *d7; - float tmp7 = *d0 - *d7; - float tmp1 = *d1 + *d6; - float tmp6 = *d1 - *d6; - float tmp2 = *d2 + *d5; - float tmp5 = *d2 - *d5; - float tmp3 = *d3 + *d4; - float tmp4 = *d3 - *d4; - - // Even part - float tmp10 = tmp0 + tmp3; // phase 2 - float tmp13 = tmp0 - tmp3; - float tmp11 = tmp1 + tmp2; - float tmp12 = tmp1 - tmp2; - - *d0 = tmp10 + tmp11; // phase 3 - *d4 = tmp10 - tmp11; - - float z1 = (tmp12 + tmp13) * 0.707106781f; // c4 - *d2 = tmp13 + z1; // phase 5 - *d6 = tmp13 - z1; - - // Odd part - tmp10 = tmp4 + tmp5; // phase 2 - tmp11 = tmp5 + tmp6; - tmp12 = tmp6 + tmp7; - - // The rotator is modified from fig 4-8 to avoid extra negations. - float z5 = (tmp10 - tmp12) * 0.382683433f; // c6 - float z2 = tmp10 * 0.541196100f + z5; // c2-c6 - float z4 = tmp12 * 1.306562965f + z5; // c2+c6 - float z3 = tmp11 * 0.707106781f; // c4 - - float z11 = tmp7 + z3; // phase 5 - float z13 = tmp7 - z3; - - *d5 = z13 + z2; // phase 6 - *d3 = z13 - z2; - *d1 = z11 + z4; - *d7 = z11 - z4; -} - -static void jo_calcBits(int val, unsigned short bits[2]) { - int tmp1 = val < 0 ? -val : val; - val = val < 0 ? val-1 : val; - bits[1] = 1; - while(tmp1 >>= 1) { - ++bits[1]; - } - bits[0] = val & ((1<0)&&(DU[end0pos]==0); --end0pos) { - } - // end0pos = first element in reverse order !=0 - if(end0pos == 0) { - jo_writeBits(fp, bitBuf, bitCnt, EOB); - return DU[0]; - } - for(i = 1; i <= end0pos; ++i) { - int startpos = i; - for (; DU[i]==0 && i<=end0pos; ++i) { - } - int nrzeroes = i-startpos; - if ( nrzeroes >= 16 ) { - int lng = nrzeroes>>4; - for (nrmarker=1; nrmarker <= lng; ++nrmarker) - jo_writeBits(fp, bitBuf, bitCnt, M16zeroes); - nrzeroes &= 15; - } - unsigned short bits[2]; - jo_calcBits(DU[i], bits); - jo_writeBits(fp, bitBuf, bitCnt, HTAC[(nrzeroes<<4)+bits[1]]); - jo_writeBits(fp, bitBuf, bitCnt, bits); - } - if(end0pos != 63) { - jo_writeBits(fp, bitBuf, bitCnt, EOB); - } - return DU[0]; -} - -int jo_write_jpg(const char *filename, const void *data, int width, int height, int comp, int quality) { - // Constants that don't pollute global namespace - static const unsigned char std_dc_luminance_nrcodes[] = {0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0}; - static const unsigned char std_dc_luminance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; - static const unsigned char std_ac_luminance_nrcodes[] = {0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d}; - static const unsigned char std_ac_luminance_values[] = { - 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, - 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, - 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, - 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, - 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, - 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, - 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa - }; - static const unsigned char std_dc_chrominance_nrcodes[] = {0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0}; - static const unsigned char std_dc_chrominance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; - static const unsigned char std_ac_chrominance_nrcodes[] = {0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77}; - static const unsigned char std_ac_chrominance_values[] = { - 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, - 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, - 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, - 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, - 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, - 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, - 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa - }; - // Huffman tables - static const unsigned short YDC_HT[256][2] = { {0,2},{2,3},{3,3},{4,3},{5,3},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9}}; - static const unsigned short UVDC_HT[256][2] = { {0,2},{1,2},{2,2},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9},{1022,10},{2046,11}}; - static const unsigned short YAC_HT[256][2] = { - {10,4},{0,2},{1,2},{4,3},{11,4},{26,5},{120,7},{248,8},{1014,10},{65410,16},{65411,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {12,4},{27,5},{121,7},{502,9},{2038,11},{65412,16},{65413,16},{65414,16},{65415,16},{65416,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {28,5},{249,8},{1015,10},{4084,12},{65417,16},{65418,16},{65419,16},{65420,16},{65421,16},{65422,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {58,6},{503,9},{4085,12},{65423,16},{65424,16},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {59,6},{1016,10},{65430,16},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {122,7},{2039,11},{65438,16},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {123,7},{4086,12},{65446,16},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {250,8},{4087,12},{65454,16},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {504,9},{32704,15},{65462,16},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {505,9},{65470,16},{65471,16},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {506,9},{65479,16},{65480,16},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {1017,10},{65488,16},{65489,16},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {1018,10},{65497,16},{65498,16},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {2040,11},{65506,16},{65507,16},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {65515,16},{65516,16},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{0,0},{0,0},{0,0},{0,0},{0,0}, - {2041,11},{65525,16},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} - }; - static const unsigned short UVAC_HT[256][2] = { - {0,2},{1,2},{4,3},{10,4},{24,5},{25,5},{56,6},{120,7},{500,9},{1014,10},{4084,12},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {11,4},{57,6},{246,8},{501,9},{2038,11},{4085,12},{65416,16},{65417,16},{65418,16},{65419,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {26,5},{247,8},{1015,10},{4086,12},{32706,15},{65420,16},{65421,16},{65422,16},{65423,16},{65424,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {27,5},{248,8},{1016,10},{4087,12},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{65430,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {58,6},{502,9},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{65438,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {59,6},{1017,10},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{65446,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {121,7},{2039,11},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{65454,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {122,7},{2040,11},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{65462,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {249,8},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{65470,16},{65471,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {503,9},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{65479,16},{65480,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {504,9},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{65488,16},{65489,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {505,9},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{65497,16},{65498,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {506,9},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{65506,16},{65507,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {2041,11},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{65515,16},{65516,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {16352,14},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{65525,16},{0,0},{0,0},{0,0},{0,0},{0,0}, - {1018,10},{32707,15},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} - }; - static const int YQT[] = {16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22,37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99}; - static const int UVQT[] = {17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99}; - static const float aasf[] = { 1.0f * 2.828427125f, 1.387039845f * 2.828427125f, 1.306562965f * 2.828427125f, 1.175875602f * 2.828427125f, 1.0f * 2.828427125f, 0.785694958f * 2.828427125f, 0.541196100f * 2.828427125f, 0.275899379f * 2.828427125f }; - int i, row, col, x, y, k, pos; - - if(!data || !filename || !width || !height || comp > 4 || comp < 1 || comp == 2) { - return 0; - } - - FILE *fp = fopen(filename, "wb"); - if(!fp) { - return 0; - } - - quality = quality ? quality : 90; - quality = quality < 1 ? 1 : quality > 100 ? 100 : quality; - quality = quality < 50 ? 5000 / quality : 200 - quality * 2; - - unsigned char YTable[64], UVTable[64]; - for(i = 0; i < 64; ++i) { - int yti = (YQT[i]*quality+50)/100; - YTable[s_jo_ZigZag[i]] = yti < 1 ? 1 : yti > 255 ? 255 : yti; - int uvti = (UVQT[i]*quality+50)/100; - UVTable[s_jo_ZigZag[i]] = uvti < 1 ? 1 : uvti > 255 ? 255 : uvti; - } - - float fdtbl_Y[64], fdtbl_UV[64]; - for(row = 0, k = 0; row < 8; ++row) { - for(col = 0; col < 8; ++col, ++k) { - fdtbl_Y[k] = 1 / (YTable [s_jo_ZigZag[k]] * aasf[row] * aasf[col]); - fdtbl_UV[k] = 1 / (UVTable[s_jo_ZigZag[k]] * aasf[row] * aasf[col]); - } - } - - // Write Headers - static const unsigned char head0[] = { 0xFF,0xD8,0xFF,0xE0,0,0x10,'J','F','I','F',0,1,1,0,0,1,0,1,0,0,0xFF,0xDB,0,0x84,0 }; - fwrite(head0, sizeof(head0), 1, fp); - fwrite(YTable, sizeof(YTable), 1, fp); - putc(1, fp); - fwrite(UVTable, sizeof(UVTable), 1, fp); - const unsigned char head1[] = { 0xFF,0xC0,0,0x11,8,(unsigned char)(height>>8),(unsigned char)(height&0xFF),(unsigned char)(width>>8),(unsigned char)(width&0xFF),3,1,0x11,0,2,0x11,1,3,0x11,1,0xFF,0xC4,0x01,0xA2,0 }; - fwrite(head1, sizeof(head1), 1, fp); - fwrite(std_dc_luminance_nrcodes+1, sizeof(std_dc_luminance_nrcodes)-1, 1, fp); - fwrite(std_dc_luminance_values, sizeof(std_dc_luminance_values), 1, fp); - putc(0x10, fp); // HTYACinfo - fwrite(std_ac_luminance_nrcodes+1, sizeof(std_ac_luminance_nrcodes)-1, 1, fp); - fwrite(std_ac_luminance_values, sizeof(std_ac_luminance_values), 1, fp); - putc(1, fp); // HTUDCinfo - fwrite(std_dc_chrominance_nrcodes+1, sizeof(std_dc_chrominance_nrcodes)-1, 1, fp); - fwrite(std_dc_chrominance_values, sizeof(std_dc_chrominance_values), 1, fp); - putc(0x11, fp); // HTUACinfo - fwrite(std_ac_chrominance_nrcodes+1, sizeof(std_ac_chrominance_nrcodes)-1, 1, fp); - fwrite(std_ac_chrominance_values, sizeof(std_ac_chrominance_values), 1, fp); - static const unsigned char head2[] = { 0xFF,0xDA,0,0xC,3,1,0,2,0x11,3,0x11,0,0x3F,0 }; - fwrite(head2, sizeof(head2), 1, fp); - - // Encode 8x8 macroblocks - const unsigned char *imageData = (const unsigned char *)data; - int DCY=0, DCU=0, DCV=0; - int bitBuf=0, bitCnt=0; - int ofsG = comp > 1 ? 1 : 0, ofsB = comp > 1 ? 2 : 0; - for(y = 0; y < height; y += 8) { - for(x = 0; x < width; x += 8) { - float YDU[64], UDU[64], VDU[64]; - for(row = y, pos = 0; row < y+8; ++row) { - for(col = x; col < x+8; ++col, ++pos) { - int p = row*width*comp + col*comp; - if(row >= height) { - p -= width*comp*(row+1 - height); - } - if(col >= width) { - p -= comp*(col+1 - width); - } - - float r = imageData[p+0], g = imageData[p+ofsG], b = imageData[p+ofsB]; - YDU[pos]=+0.29900f*r+0.58700f*g+0.11400f*b-128; - UDU[pos]=-0.16874f*r-0.33126f*g+0.50000f*b; - VDU[pos]=+0.50000f*r-0.41869f*g-0.08131f*b; - } - } - - DCY = jo_processDU(fp, &bitBuf, &bitCnt, YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT); - DCU = jo_processDU(fp, &bitBuf, &bitCnt, UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); - DCV = jo_processDU(fp, &bitBuf, &bitCnt, VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); - } - } - - // Do the bit alignment of the EOI marker - static const unsigned short fillBits[] = {0x7F, 7}; - jo_writeBits(fp, &bitBuf, &bitCnt, fillBits); - - // EOI - putc(0xFF, fp); - putc(0xD9, fp); - - fclose(fp); - return 1; -} - -#endif - diff --git a/lib/SOIL2/pkm_helper.h b/lib/SOIL2/pkm_helper.h deleted file mode 100644 index 0143e3f37..000000000 --- a/lib/SOIL2/pkm_helper.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef PKM_HELPER_H -#define PKM_HELPER_H - -typedef struct { - char aName[6]; - unsigned short iBlank; - unsigned char iPaddedWidthMSB; - unsigned char iPaddedWidthLSB; - unsigned char iPaddedHeightMSB; - unsigned char iPaddedHeightLSB; - unsigned char iWidthMSB; - unsigned char iWidthLSB; - unsigned char iHeightMSB; - unsigned char iHeightLSB; -} PKMHeader; - -#define PKM_HEADER_SIZE 16 - -#endif diff --git a/lib/SOIL2/pvr_helper.h b/lib/SOIL2/pvr_helper.h deleted file mode 100644 index bace1f38f..000000000 --- a/lib/SOIL2/pvr_helper.h +++ /dev/null @@ -1,264 +0,0 @@ -#ifndef PVR_HELPER_H -#define PVR_HELPER_H - -// Taken from PowerVR SDK - -/*!*************************************************************************** - Describes the header of a PVR header-texture - *****************************************************************************/ -typedef struct -{ - unsigned int dwHeaderSize; /*!< size of the structure */ - unsigned int dwHeight; /*!< height of surface to be created */ - unsigned int dwWidth; /*!< width of input surface */ - unsigned int dwMipMapCount; /*!< number of mip-map levels requested */ - unsigned int dwpfFlags; /*!< pixel format flags */ - unsigned int dwTextureDataSize; /*!< Total size in bytes */ - unsigned int dwBitCount; /*!< number of bits per pixel */ - unsigned int dwRBitMask; /*!< mask for red bit */ - unsigned int dwGBitMask; /*!< mask for green bits */ - unsigned int dwBBitMask; /*!< mask for blue bits */ - unsigned int dwAlphaBitMask; /*!< mask for alpha channel */ - unsigned int dwPVR; /*!< magic number identifying pvr file */ - unsigned int dwNumSurfs; /*!< the number of surfaces present in the pvr */ -} PVR_Texture_Header; - -/***************************************************************************** - * ENUMS - *****************************************************************************/ - -enum PixelType -{ - MGLPT_ARGB_4444 = 0x00, - MGLPT_ARGB_1555, - MGLPT_RGB_565, - MGLPT_RGB_555, - MGLPT_RGB_888, - MGLPT_ARGB_8888, - MGLPT_ARGB_8332, - MGLPT_I_8, - MGLPT_AI_88, - MGLPT_1_BPP, - MGLPT_VY1UY0, - MGLPT_Y1VY0U, - MGLPT_PVRTC2, - MGLPT_PVRTC4, - MGLPT_PVRTC2_2, - MGLPT_PVRTC2_4, - - OGL_RGBA_4444= 0x10, - OGL_RGBA_5551, - OGL_RGBA_8888, - OGL_RGB_565, - OGL_RGB_555, - OGL_RGB_888, - OGL_I_8, - OGL_AI_88, - OGL_PVRTC2, - OGL_PVRTC4, - - // OGL_BGRA_8888 extension - OGL_BGRA_8888, - - D3D_DXT1 = 0x20, - D3D_DXT2, - D3D_DXT3, - D3D_DXT4, - D3D_DXT5, - - D3D_RGB_332, - D3D_AI_44, - D3D_LVU_655, - D3D_XLVU_8888, - D3D_QWVU_8888, - - //10 bits per channel - D3D_ABGR_2101010, - D3D_ARGB_2101010, - D3D_AWVU_2101010, - - //16 bits per channel - D3D_GR_1616, - D3D_VU_1616, - D3D_ABGR_16161616, - - //HDR formats - D3D_R16F, - D3D_GR_1616F, - D3D_ABGR_16161616F, - - //32 bits per channel - D3D_R32F, - D3D_GR_3232F, - D3D_ABGR_32323232F, - - // Ericsson - ETC_RGB_4BPP, - ETC_RGBA_EXPLICIT, - ETC_RGBA_INTERPOLATED, - - // DX10 - - - ePT_DX10_R32G32B32A32_FLOAT= 0x50, - ePT_DX10_R32G32B32A32_UINT , - ePT_DX10_R32G32B32A32_SINT, - - ePT_DX10_R32G32B32_FLOAT, - ePT_DX10_R32G32B32_UINT, - ePT_DX10_R32G32B32_SINT, - - ePT_DX10_R16G16B16A16_FLOAT , - ePT_DX10_R16G16B16A16_UNORM, - ePT_DX10_R16G16B16A16_UINT , - ePT_DX10_R16G16B16A16_SNORM , - ePT_DX10_R16G16B16A16_SINT , - - ePT_DX10_R32G32_FLOAT , - ePT_DX10_R32G32_UINT , - ePT_DX10_R32G32_SINT , - - ePT_DX10_R10G10B10A2_UNORM , - ePT_DX10_R10G10B10A2_UINT , - - ePT_DX10_R11G11B10_FLOAT , - - ePT_DX10_R8G8B8A8_UNORM , - ePT_DX10_R8G8B8A8_UNORM_SRGB , - ePT_DX10_R8G8B8A8_UINT , - ePT_DX10_R8G8B8A8_SNORM , - ePT_DX10_R8G8B8A8_SINT , - - ePT_DX10_R16G16_FLOAT , - ePT_DX10_R16G16_UNORM , - ePT_DX10_R16G16_UINT , - ePT_DX10_R16G16_SNORM , - ePT_DX10_R16G16_SINT , - - ePT_DX10_R32_FLOAT , - ePT_DX10_R32_UINT , - ePT_DX10_R32_SINT , - - ePT_DX10_R8G8_UNORM , - ePT_DX10_R8G8_UINT , - ePT_DX10_R8G8_SNORM , - ePT_DX10_R8G8_SINT , - - ePT_DX10_R16_FLOAT , - ePT_DX10_R16_UNORM , - ePT_DX10_R16_UINT , - ePT_DX10_R16_SNORM , - ePT_DX10_R16_SINT , - - ePT_DX10_R8_UNORM, - ePT_DX10_R8_UINT, - ePT_DX10_R8_SNORM, - ePT_DX10_R8_SINT, - - ePT_DX10_A8_UNORM, - ePT_DX10_R1_UNORM, - ePT_DX10_R9G9B9E5_SHAREDEXP, - ePT_DX10_R8G8_B8G8_UNORM, - ePT_DX10_G8R8_G8B8_UNORM, - - ePT_DX10_BC1_UNORM, - ePT_DX10_BC1_UNORM_SRGB, - - ePT_DX10_BC2_UNORM, - ePT_DX10_BC2_UNORM_SRGB, - - ePT_DX10_BC3_UNORM, - ePT_DX10_BC3_UNORM_SRGB, - - ePT_DX10_BC4_UNORM, - ePT_DX10_BC4_SNORM, - - ePT_DX10_BC5_UNORM, - ePT_DX10_BC5_SNORM, - - //ePT_DX10_B5G6R5_UNORM, // defined but obsolete - won't actually load in DX10 - //ePT_DX10_B5G5R5A1_UNORM, - //ePT_DX10_B8G8R8A8_UNORM, - //ePT_DX10_B8G8R8X8_UNORM, - - // OpenVG - - /* RGB{A,X} channel ordering */ - ePT_VG_sRGBX_8888 = 0x90, - ePT_VG_sRGBA_8888, - ePT_VG_sRGBA_8888_PRE, - ePT_VG_sRGB_565, - ePT_VG_sRGBA_5551, - ePT_VG_sRGBA_4444, - ePT_VG_sL_8, - ePT_VG_lRGBX_8888, - ePT_VG_lRGBA_8888, - ePT_VG_lRGBA_8888_PRE, - ePT_VG_lL_8, - ePT_VG_A_8, - ePT_VG_BW_1, - - /* {A,X}RGB channel ordering */ - ePT_VG_sXRGB_8888, - ePT_VG_sARGB_8888, - ePT_VG_sARGB_8888_PRE, - ePT_VG_sARGB_1555, - ePT_VG_sARGB_4444, - ePT_VG_lXRGB_8888, - ePT_VG_lARGB_8888, - ePT_VG_lARGB_8888_PRE, - - /* BGR{A,X} channel ordering */ - ePT_VG_sBGRX_8888, - ePT_VG_sBGRA_8888, - ePT_VG_sBGRA_8888_PRE, - ePT_VG_sBGR_565, - ePT_VG_sBGRA_5551, - ePT_VG_sBGRA_4444, - ePT_VG_lBGRX_8888, - ePT_VG_lBGRA_8888, - ePT_VG_lBGRA_8888_PRE, - - /* {A,X}BGR channel ordering */ - ePT_VG_sXBGR_8888, - ePT_VG_sABGR_8888 , - ePT_VG_sABGR_8888_PRE, - ePT_VG_sABGR_1555, - ePT_VG_sABGR_4444, - ePT_VG_lXBGR_8888, - ePT_VG_lABGR_8888, - ePT_VG_lABGR_8888_PRE, - - // max cap for iterating - END_OF_PIXEL_TYPES, - - MGLPT_NOTYPE = 0xff - -}; - -/***************************************************************************** - * constants - *****************************************************************************/ - -#define PVRTEX_MIPMAP (1<<8) // has mip map levels -#define PVRTEX_TWIDDLE (1<<9) // is twiddled -#define PVRTEX_BUMPMAP (1<<10) // has normals encoded for a bump map -#define PVRTEX_TILING (1<<11) // is bordered for tiled pvr -#define PVRTEX_CUBEMAP (1<<12) // is a cubemap/skybox -#define PVRTEX_FALSEMIPCOL (1<<13) // -#define PVRTEX_VOLUME (1<<14) -#define PVRTEX_PIXELTYPE 0xff // pixel type is always in the last 16bits of the flags -#define PVRTEX_IDENTIFIER 0x21525650 // the pvr identifier is the characters 'P','V','R' - -#define PVRTEX_V1_HEADER_SIZE 44 // old header size was 44 for identification purposes - -#define PVRTC2_MIN_TEXWIDTH 16 -#define PVRTC2_MIN_TEXHEIGHT 8 -#define PVRTC4_MIN_TEXWIDTH 8 -#define PVRTC4_MIN_TEXHEIGHT 8 -#define ETC_MIN_TEXWIDTH 4 -#define ETC_MIN_TEXHEIGHT 4 -#define DXT_MIN_TEXWIDTH 4 -#define DXT_MIN_TEXHEIGHT 4 - -#endif diff --git a/lib/SOIL2/stb_image.h b/lib/SOIL2/stb_image.h deleted file mode 100644 index fa446cbff..000000000 --- a/lib/SOIL2/stb_image.h +++ /dev/null @@ -1,7240 +0,0 @@ -/* stb_image - v2.15 - public domain image loader - http://nothings.org/stb_image.h - no warranty implied; use at your own risk - - Do this: - #define STB_IMAGE_IMPLEMENTATION - before you include this file in *one* C or C++ file to create the implementation. - - // i.e. it should look like this: - #include ... - #include ... - #include ... - #define STB_IMAGE_IMPLEMENTATION - #include "stb_image.h" - - You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. - And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free - - - QUICK NOTES: - Primarily of interest to game developers and other people who can - avoid problematic images and only need the trivial interface - - JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) - PNG 1/2/4/8/16-bit-per-channel - - TGA (not sure what subset, if a subset) - BMP non-1bpp, non-RLE - PSD (composited view only, no extra channels, 8/16 bit-per-channel) - - GIF (*comp always reports as 4-channel) - HDR (radiance rgbE format) - PIC (Softimage PIC) - PNM (PPM and PGM binary only) - - Animated GIF still needs a proper API, but here's one way to do it: - http://gist.github.com/urraka/685d9a6340b26b830d49 - - - decode from memory or through FILE (define STBI_NO_STDIO to remove code) - - decode from arbitrary I/O callbacks - - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON) - - Full documentation under "DOCUMENTATION" below. - - -LICENSE - - See end of file for license information. - -RECENT REVISION HISTORY: - - 2.15 (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC - 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs - 2.13 (2016-12-04) experimental 16-bit API, only for PNG so far; fixes - 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes - 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64 - RGB-format JPEG; remove white matting in PSD; - allocate large structures on the stack; - correct channel count for PNG & BMP - 2.10 (2016-01-22) avoid warning introduced in 2.09 - 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED - 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA - 2.07 (2015-09-13) partial animated GIF support - limited 16-bit PSD support - minor bugs, code cleanup, and compiler warnings - - See end of file for full revision history. - - - ============================ Contributors ========================= - - Image formats Extensions, features - Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info) - Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info) - Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG) - Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks) - Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG) - Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) - Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) - github:urraka (animated gif) Junggon Kim (PNM comments) - Daniel Gibson (16-bit TGA) - socks-the-fox (16-bit PNG) - Jeremy Sawicki (handle all ImageNet JPGs) - Optimizations & bugfixes - Fabian "ryg" Giesen - Arseny Kapoulkine - - Bug & warning fixes - Marc LeBlanc David Woo Guillaume George Martins Mozeiko - Christpher Lloyd Jerry Jansson Joseph Thomson Phil Jordan - Dave Moore Roy Eltham Hayaki Saito Nathan Reed - Won Chun Luke Graham Johan Duparc Nick Verigakis - the Horde3D community Thomas Ruf Ronny Chevalier Baldur Karlsson - Janez Zemva John Bartholomew Michal Cichon github:rlyeh - Jonathan Blow Ken Hamada Tero Hanninen github:romigrou - Laurent Gomila Cort Stratton Sergio Gonzalez github:svdijk - Aruelien Pocheville Thibault Reuille Cass Everitt github:snagar - Ryamond Barbiero Paul Du Bois Engin Manap github:Zelex - Michaelangel007@github Philipp Wiesemann Dale Weiler github:grim210 - Oriol Ferrer Mesia Josh Tobin Matthew Gregan github:sammyhw - Blazej Dariusz Roszkowski Gregory Mullen github:phprus - -*/ - -#ifndef STBI_INCLUDE_STB_IMAGE_H -#define STBI_INCLUDE_STB_IMAGE_H - -// DOCUMENTATION -// -// Limitations: -// - no 16-bit-per-channel PNG -// - no 12-bit-per-channel JPEG -// - no JPEGs with arithmetic coding -// - no 1-bit BMP -// - GIF always returns *comp=4 -// -// Basic usage (see HDR discussion below for HDR usage): -// int x,y,n; -// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); -// // ... process data if not NULL ... -// // ... x = width, y = height, n = # 8-bit components per pixel ... -// // ... replace '0' with '1'..'4' to force that many components per pixel -// // ... but 'n' will always be the number that it would have been if you said 0 -// stbi_image_free(data) -// -// Standard parameters: -// int *x -- outputs image width in pixels -// int *y -- outputs image height in pixels -// int *channels_in_file -- outputs # of image components in image file -// int desired_channels -- if non-zero, # of image components requested in result -// -// The return value from an image loader is an 'unsigned char *' which points -// to the pixel data, or NULL on an allocation failure or if the image is -// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, -// with each pixel consisting of N interleaved 8-bit components; the first -// pixel pointed to is top-left-most in the image. There is no padding between -// image scanlines or between pixels, regardless of format. The number of -// components N is 'req_comp' if req_comp is non-zero, or *comp otherwise. -// If req_comp is non-zero, *comp has the number of components that _would_ -// have been output otherwise. E.g. if you set req_comp to 4, you will always -// get RGBA output, but you can check *comp to see if it's trivially opaque -// because e.g. there were only 3 channels in the source image. -// -// An output image with N components has the following components interleaved -// in this order in each pixel: -// -// N=#comp components -// 1 grey -// 2 grey, alpha -// 3 red, green, blue -// 4 red, green, blue, alpha -// -// If image loading fails for any reason, the return value will be NULL, -// and *x, *y, *comp will be unchanged. The function stbi_failure_reason() -// can be queried for an extremely brief, end-user unfriendly explanation -// of why the load failed. Define STBI_NO_FAILURE_STRINGS to avoid -// compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly -// more user-friendly ones. -// -// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. -// -// =========================================================================== -// -// Philosophy -// -// stb libraries are designed with the following priorities: -// -// 1. easy to use -// 2. easy to maintain -// 3. good performance -// -// Sometimes I let "good performance" creep up in priority over "easy to maintain", -// and for best performance I may provide less-easy-to-use APIs that give higher -// performance, in addition to the easy to use ones. Nevertheless, it's important -// to keep in mind that from the standpoint of you, a client of this library, -// all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all. -// -// Some secondary priorities arise directly from the first two, some of which -// make more explicit reasons why performance can't be emphasized. -// -// - Portable ("ease of use") -// - Small source code footprint ("easy to maintain") -// - No dependencies ("ease of use") -// -// =========================================================================== -// -// I/O callbacks -// -// I/O callbacks allow you to read from arbitrary sources, like packaged -// files or some other source. Data read from callbacks are processed -// through a small internal buffer (currently 128 bytes) to try to reduce -// overhead. -// -// The three functions you must define are "read" (reads some bytes of data), -// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). -// -// =========================================================================== -// -// SIMD support -// -// The JPEG decoder will try to automatically use SIMD kernels on x86 when -// supported by the compiler. For ARM Neon support, you must explicitly -// request it. -// -// (The old do-it-yourself SIMD API is no longer supported in the current -// code.) -// -// On x86, SSE2 will automatically be used when available based on a run-time -// test; if not, the generic C versions are used as a fall-back. On ARM targets, -// the typical path is to have separate builds for NEON and non-NEON devices -// (at least this is true for iOS and Android). Therefore, the NEON support is -// toggled by a build flag: define STBI_NEON to get NEON loops. -// -// If for some reason you do not want to use any of SIMD code, or if -// you have issues compiling it, you can disable it entirely by -// defining STBI_NO_SIMD. -// -// =========================================================================== -// -// HDR image support (disable by defining STBI_NO_HDR) -// -// stb_image now supports loading HDR images in general, and currently -// the Radiance .HDR file format, although the support is provided -// generically. You can still load any file through the existing interface; -// if you attempt to load an HDR file, it will be automatically remapped to -// LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; -// both of these constants can be reconfigured through this interface: -// -// stbi_hdr_to_ldr_gamma(2.2f); -// stbi_hdr_to_ldr_scale(1.0f); -// -// (note, do not use _inverse_ constants; stbi_image will invert them -// appropriately). -// -// Additionally, there is a new, parallel interface for loading files as -// (linear) floats to preserve the full dynamic range: -// -// float *data = stbi_loadf(filename, &x, &y, &n, 0); -// -// If you load LDR images through this interface, those images will -// be promoted to floating point values, run through the inverse of -// constants corresponding to the above: -// -// stbi_ldr_to_hdr_scale(1.0f); -// stbi_ldr_to_hdr_gamma(2.2f); -// -// Finally, given a filename (or an open file or memory block--see header -// file for details) containing image data, you can query for the "most -// appropriate" interface to use (that is, whether the image is HDR or -// not), using: -// -// stbi_is_hdr(char *filename); -// -// =========================================================================== -// -// iPhone PNG support: -// -// By default we convert iphone-formatted PNGs back to RGB, even though -// they are internally encoded differently. You can disable this conversion -// by by calling stbi_convert_iphone_png_to_rgb(0), in which case -// you will always just get the native iphone "format" through (which -// is BGR stored in RGB). -// -// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per -// pixel to remove any premultiplied alpha *only* if the image file explicitly -// says there's premultiplied data (currently only happens in iPhone images, -// and only if iPhone convert-to-rgb processing is on). -// -// =========================================================================== -// -// ADDITIONAL CONFIGURATION -// -// - You can suppress implementation of any of the decoders to reduce -// your code footprint by #defining one or more of the following -// symbols before creating the implementation. -// -// STBI_NO_JPEG -// STBI_NO_PNG -// STBI_NO_BMP -// STBI_NO_PSD -// STBI_NO_TGA -// STBI_NO_GIF -// STBI_NO_HDR -// STBI_NO_PIC -// STBI_NO_PNM (.ppm and .pgm) -// -// - You can request *only* certain decoders and suppress all other ones -// (this will be more forward-compatible, as addition of new decoders -// doesn't require you to disable them explicitly): -// -// STBI_ONLY_JPEG -// STBI_ONLY_PNG -// STBI_ONLY_BMP -// STBI_ONLY_PSD -// STBI_ONLY_TGA -// STBI_ONLY_GIF -// STBI_ONLY_HDR -// STBI_ONLY_PIC -// STBI_ONLY_PNM (.ppm and .pgm) -// -// - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still -// want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB -// - - -#ifndef STBI_NO_STDIO -#include -#endif // STBI_NO_STDIO - -#define STBI_VERSION 1 - -enum -{ - STBI_default = 0, // only used for req_comp - - STBI_grey = 1, - STBI_grey_alpha = 2, - STBI_rgb = 3, - STBI_rgb_alpha = 4 -}; - -typedef unsigned char stbi_uc; -typedef unsigned short stbi_us; - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef STB_IMAGE_STATIC -#define STBIDEF static -#else -#define STBIDEF extern -#endif - -////////////////////////////////////////////////////////////////////////////// -// -// PRIMARY API - works on images of any type -// - -// -// load image by filename, open file, or memory buffer -// - -typedef struct -{ - int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read - void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative - int (*eof) (void *user); // returns nonzero if we are at end of file/data -} stbi_io_callbacks; - -//////////////////////////////////// -// -// 8-bits-per-channel interface -// - -STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); -STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *channels_in_file, int desired_channels); -STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *channels_in_file, int desired_channels); - -#ifndef STBI_NO_STDIO -STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); -// for stbi_load_from_file, file pointer is left pointing immediately after image -#endif - -//////////////////////////////////// -// -// 16-bits-per-channel interface -// - -STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); -#ifndef STBI_NO_STDIO -STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); -#endif -// @TODO the other variants - -//////////////////////////////////// -// -// float-per-channel interface -// -#ifndef STBI_NO_LINEAR - STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); - STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); - STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); - - #ifndef STBI_NO_STDIO - STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); - #endif -#endif - -#ifndef STBI_NO_HDR - STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); - STBIDEF void stbi_hdr_to_ldr_scale(float scale); -#endif // STBI_NO_HDR - -#ifndef STBI_NO_LINEAR - STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); - STBIDEF void stbi_ldr_to_hdr_scale(float scale); -#endif // STBI_NO_LINEAR - -// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR -STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); -STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); -#ifndef STBI_NO_STDIO -STBIDEF int stbi_is_hdr (char const *filename); -STBIDEF int stbi_is_hdr_from_file(FILE *f); -#endif // STBI_NO_STDIO - - -// get a VERY brief reason for failure -// NOT THREADSAFE -STBIDEF const char *stbi_failure_reason (void); - -// free the loaded image -- this is just free() -STBIDEF void stbi_image_free (void *retval_from_stbi_load); - -// get image dimensions & components without fully decoding -STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); -STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); - -#ifndef STBI_NO_STDIO -STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); -STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); - -#endif - - - -// for image formats that explicitly notate that they have premultiplied alpha, -// we just return the colors as stored in the file. set this flag to force -// unpremultiplication. results are undefined if the unpremultiply overflow. -STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); - -// indicate whether we should process iphone images back to canonical format, -// or just pass them through "as-is" -STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); - -// flip the image vertically, so the first pixel in the output array is the bottom left -STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); - -// ZLIB client - used by PNG, available for other purposes - -STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); -STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); -STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); -STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); - -STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); -STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); - -#ifndef STBI_NO_DDS -#include "stbi_DDS.h" -#endif - -#ifndef STBI_NO_PVR -#include "stbi_pvr.h" -#endif - -#ifndef STBI_NO_PKM -#include "stbi_pkm.h" -#endif - -#ifndef STBI_NO_EXT -#include "stbi_ext.h" -#endif - -#ifdef __cplusplus -} -#endif - -// -// -//// end header file ///////////////////////////////////////////////////// -#endif // STBI_INCLUDE_STB_IMAGE_H - -#ifdef STB_IMAGE_IMPLEMENTATION - -#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ - || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ - || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ - || defined(STBI_ONLY_ZLIB) - #ifndef STBI_ONLY_JPEG - #define STBI_NO_JPEG - #endif - #ifndef STBI_ONLY_PNG - #define STBI_NO_PNG - #endif - #ifndef STBI_ONLY_BMP - #define STBI_NO_BMP - #endif - #ifndef STBI_ONLY_PSD - #define STBI_NO_PSD - #endif - #ifndef STBI_ONLY_TGA - #define STBI_NO_TGA - #endif - #ifndef STBI_ONLY_GIF - #define STBI_NO_GIF - #endif - #ifndef STBI_ONLY_HDR - #define STBI_NO_HDR - #endif - #ifndef STBI_ONLY_PIC - #define STBI_NO_PIC - #endif - #ifndef STBI_ONLY_PNM - #define STBI_NO_PNM - #endif -#endif - -#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) -#define STBI_NO_ZLIB -#endif - - -#include -#include // ptrdiff_t on osx -#include -#include -#include - -#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) -#include // ldexp -#endif - -#ifndef STBI_NO_STDIO -#include -#endif - -#ifndef STBI_ASSERT -#include -#define STBI_ASSERT(x) assert(x) -#endif - - -#ifndef _MSC_VER - #ifdef __cplusplus - #define stbi_inline inline - #else - #define stbi_inline - #endif -#else - #define stbi_inline __forceinline -#endif - - -#ifdef _MSC_VER -typedef unsigned short stbi__uint16; -typedef signed short stbi__int16; -typedef unsigned int stbi__uint32; -typedef signed int stbi__int32; -#else -#include -typedef uint16_t stbi__uint16; -typedef int16_t stbi__int16; -typedef uint32_t stbi__uint32; -typedef int32_t stbi__int32; -#endif - -// should produce compiler error if size is wrong -typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; - -#ifdef _MSC_VER -#define STBI_NOTUSED(v) (void)(v) -#else -#define STBI_NOTUSED(v) (void)sizeof(v) -#endif - -#ifdef _MSC_VER -#define STBI_HAS_LROTL -#endif - -#ifdef STBI_HAS_LROTL - #define stbi_lrot(x,y) _lrotl(x,y) -#else - #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (32 - (y)))) -#endif - -#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) -// ok -#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) -// ok -#else -#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." -#endif - -#ifndef STBI_MALLOC -#define STBI_MALLOC(sz) malloc(sz) -#define STBI_REALLOC(p,newsz) realloc(p,newsz) -#define STBI_FREE(p) free(p) -#endif - -#ifndef STBI_REALLOC_SIZED -#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) -#endif - -// x86/x64 detection -#if defined(__x86_64__) || defined(_M_X64) -#define STBI__X64_TARGET -#elif defined(__i386) || defined(_M_IX86) -#define STBI__X86_TARGET -#endif - -#if defined(__GNUC__) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) -// NOTE: not clear do we actually need this for the 64-bit path? -// gcc doesn't support sse2 intrinsics unless you compile with -msse2, -// (but compiling with -msse2 allows the compiler to use SSE2 everywhere; -// this is just broken and gcc are jerks for not fixing it properly -// http://www.virtualdub.org/blog/pivot/entry.php?id=363 ) -#define STBI_NO_SIMD -#endif - -#if defined(__MINGW32__) && !defined(__x86_64__) && !defined(STBI_NO_SIMD) -#define STBI_MINGW_ENABLE_SSE2 -#define STBI_FORCE_STACK_ALIGN __attribute__((force_align_arg_pointer)) -#else -#define STBI_FORCE_STACK_ALIGN -#endif - -#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) -#define STBI_SSE2 -#include - -#ifdef _MSC_VER - -#if _MSC_VER >= 1400 // not VC6 -#include // __cpuid -static int stbi__cpuid3(void) -{ - int info[4]; - __cpuid(info,1); - return info[3]; -} -#else -static int stbi__cpuid3(void) -{ - int res; - __asm { - mov eax,1 - cpuid - mov res,edx - } - return res; -} -#endif - -#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name - -static int stbi__sse2_available() -{ - int info3 = stbi__cpuid3(); - return ((info3 >> 26) & 1) != 0; -} -#else // assume GCC-style if not VC++ -#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) - -static int stbi__sse2_available() -{ - // If we're even attempting to compile this on GCC/Clang, that means - // -msse2 is on, which means the compiler is allowed to use SSE2 - // instructions at will, and so are we. - return 1; -} -#endif -#endif - -// ARM NEON -#if defined(STBI_NO_SIMD) && defined(STBI_NEON) -#undef STBI_NEON -#endif - -#ifdef STBI_NEON -#include -// assume GCC or Clang on ARM targets -#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) -#endif - -#ifndef STBI_SIMD_ALIGN -#define STBI_SIMD_ALIGN(type, name) type name -#endif - -/////////////////////////////////////////////// -// -// stbi__context struct and start_xxx functions - -// stbi__context structure is our basic context used by all images, so it -// contains all the IO context, plus some basic image information -typedef struct -{ - stbi__uint32 img_x, img_y; - int img_n, img_out_n; - - stbi_io_callbacks io; - void *io_user_data; - - int read_from_callbacks; - int buflen; - stbi_uc buffer_start[128]; - - stbi_uc *img_buffer, *img_buffer_end; - stbi_uc *img_buffer_original, *img_buffer_original_end; -} stbi__context; - - -static void stbi__refill_buffer(stbi__context *s); - -// initialize a memory-decode context -static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) -{ - s->io.read = NULL; - s->read_from_callbacks = 0; - s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; - s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; -} - -// initialize a callback-based context -static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) -{ - s->io = *c; - s->io_user_data = user; - s->buflen = sizeof(s->buffer_start); - s->read_from_callbacks = 1; - s->img_buffer_original = s->buffer_start; - stbi__refill_buffer(s); - s->img_buffer_original_end = s->img_buffer_end; -} - -#ifndef STBI_NO_STDIO - -static int stbi__stdio_read(void *user, char *data, int size) -{ - return (int) fread(data,1,size,(FILE*) user); -} - -static void stbi__stdio_skip(void *user, int n) -{ - fseek((FILE*) user, n, SEEK_CUR); -} - -static int stbi__stdio_eof(void *user) -{ - return feof((FILE*) user); -} - -static stbi_io_callbacks stbi__stdio_callbacks = -{ - stbi__stdio_read, - stbi__stdio_skip, - stbi__stdio_eof, -}; - -static void stbi__start_file(stbi__context *s, FILE *f) -{ - stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); -} - -//static void stop_file(stbi__context *s) { } - -#endif // !STBI_NO_STDIO - -static void stbi__rewind(stbi__context *s) -{ - // conceptually rewind SHOULD rewind to the beginning of the stream, - // but we just rewind to the beginning of the initial buffer, because - // we only use it after doing 'test', which only ever looks at at most 92 bytes - s->img_buffer = s->img_buffer_original; - s->img_buffer_end = s->img_buffer_original_end; -} - -enum -{ - STBI_ORDER_RGB, - STBI_ORDER_BGR -}; - -typedef struct -{ - int bits_per_channel; - int num_channels; - int channel_order; -} stbi__result_info; - -#ifndef STBI_NO_JPEG -static int stbi__jpeg_test(stbi__context *s); -static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_PNG -static int stbi__png_test(stbi__context *s); -static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_BMP -static int stbi__bmp_test(stbi__context *s); -static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_TGA -static int stbi__tga_test(stbi__context *s); -static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_PSD -static int stbi__psd_test(stbi__context *s); -static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc); -static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_HDR -static int stbi__hdr_test(stbi__context *s); -static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_PIC -static int stbi__pic_test(stbi__context *s); -static void *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_GIF -static int stbi__gif_test(stbi__context *s); -static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_PNM -static int stbi__pnm_test(stbi__context *s); -static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_DDS -static int stbi__dds_test(stbi__context *s); -static void *stbi__dds_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); -static int stbi__dds_info(stbi__context *s, int *x, int *y, int *comp, int *iscompressed); -#endif - -#ifndef STBI_NO_PVR -static int stbi__pvr_test(stbi__context *s); -static void *stbi__pvr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); -static int stbi__pvr_info(stbi__context *s, int *x, int *y, int *comp, int * iscompressed); -#endif - -#ifndef STBI_NO_PKM -static int stbi__pkm_test(stbi__context *s); -static void *stbi__pkm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); -static int stbi__pkm_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -// this is not threadsafe -static const char *stbi__g_failure_reason; - -STBIDEF const char *stbi_failure_reason(void) -{ - return stbi__g_failure_reason; -} - -static int stbi__err(const char *str) -{ - stbi__g_failure_reason = str; - return 0; -} - -static void *stbi__malloc(size_t size) -{ - return STBI_MALLOC(size); -} - -// stb_image uses ints pervasively, including for offset calculations. -// therefore the largest decoded image size we can support with the -// current code, even on 64-bit targets, is INT_MAX. this is not a -// significant limitation for the intended use case. -// -// we do, however, need to make sure our size calculations don't -// overflow. hence a few helper functions for size calculations that -// multiply integers together, making sure that they're non-negative -// and no overflow occurs. - -// return 1 if the sum is valid, 0 on overflow. -// negative terms are considered invalid. -static int stbi__addsizes_valid(int a, int b) -{ - if (b < 0) return 0; - // now 0 <= b <= INT_MAX, hence also - // 0 <= INT_MAX - b <= INTMAX. - // And "a + b <= INT_MAX" (which might overflow) is the - // same as a <= INT_MAX - b (no overflow) - return a <= INT_MAX - b; -} - -// returns 1 if the product is valid, 0 on overflow. -// negative factors are considered invalid. -static int stbi__mul2sizes_valid(int a, int b) -{ - if (a < 0 || b < 0) return 0; - if (b == 0) return 1; // mul-by-0 is always safe - // portable way to check for no overflows in a*b - return a <= INT_MAX/b; -} - -// returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow -static int stbi__mad2sizes_valid(int a, int b, int add) -{ - return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add); -} - -// returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow -static int stbi__mad3sizes_valid(int a, int b, int c, int add) -{ - return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && - stbi__addsizes_valid(a*b*c, add); -} - -// returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow -static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add) -{ - return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && - stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add); -} - -// mallocs with size overflow checking -static void *stbi__malloc_mad2(int a, int b, int add) -{ - if (!stbi__mad2sizes_valid(a, b, add)) return NULL; - return stbi__malloc(a*b + add); -} - -static void *stbi__malloc_mad3(int a, int b, int c, int add) -{ - if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL; - return stbi__malloc(a*b*c + add); -} - -static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) -{ - if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL; - return stbi__malloc(a*b*c*d + add); -} - -// stbi__err - error -// stbi__errpf - error returning pointer to float -// stbi__errpuc - error returning pointer to unsigned char - -#ifdef STBI_NO_FAILURE_STRINGS - #define stbi__err(x,y) 0 -#elif defined(STBI_FAILURE_USERMSG) - #define stbi__err(x,y) stbi__err(y) -#else - #define stbi__err(x,y) stbi__err(x) -#endif - -#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) -#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) - -STBIDEF void stbi_image_free(void *retval_from_stbi_load) -{ - STBI_FREE(retval_from_stbi_load); -} - -#ifndef STBI_NO_LINEAR -static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); -#endif - -#ifndef STBI_NO_HDR -static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); -#endif - -static int stbi__vertically_flip_on_load = 0; - -STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) -{ - stbi__vertically_flip_on_load = flag_true_if_should_flip; -} - -static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) -{ - memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields - ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed - ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order - ri->num_channels = 0; - - #ifndef STBI_NO_JPEG - if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri); - #endif - #ifndef STBI_NO_PNG - if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, ri); - #endif - #ifndef STBI_NO_BMP - if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp, ri); - #endif - #ifndef STBI_NO_GIF - if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp, ri); - #endif - #ifndef STBI_NO_PSD - if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc); - #endif - #ifndef STBI_NO_PIC - if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp, ri); - #endif - #ifndef STBI_NO_PNM - if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp, ri); - #endif - #ifndef STBI_NO_DDS - if (stbi__dds_test(s)) return stbi__dds_load(s,x,y,comp,req_comp); - #endif - #ifndef STBI_NO_PVR - if (stbi__pvr_test(s)) return stbi__pvr_load(s,x,y,comp,req_comp); - #endif - #ifndef STBI_NO_PKM - if (stbi__pkm_test(s)) return stbi__pkm_load(s,x,y,comp,req_comp); - #endif - - #ifndef STBI_NO_HDR - if (stbi__hdr_test(s)) { - float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri); - return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); - } - #endif - - #ifndef STBI_NO_TGA - // test tga last because it's a crappy test! - if (stbi__tga_test(s)) - return stbi__tga_load(s,x,y,comp,req_comp, ri); - #endif - - return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); -} - -static stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels) -{ - int i; - int img_len = w * h * channels; - stbi_uc *reduced; - - reduced = (stbi_uc *) stbi__malloc(img_len); - if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory"); - - for (i = 0; i < img_len; ++i) - reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling - - STBI_FREE(orig); - return reduced; -} - -static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels) -{ - int i; - int img_len = w * h * channels; - stbi__uint16 *enlarged; - - enlarged = (stbi__uint16 *) stbi__malloc(img_len*2); - if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); - - for (i = 0; i < img_len; ++i) - enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff - - STBI_FREE(orig); - return enlarged; -} - -static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - stbi__result_info ri; - void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8); - - if (result == NULL) - return NULL; - - if (ri.bits_per_channel != 8) { - STBI_ASSERT(ri.bits_per_channel == 16); - result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp); - ri.bits_per_channel = 8; - } - - // @TODO: move stbi__convert_format to here - - if (stbi__vertically_flip_on_load) { - int w = *x, h = *y; - int channels = req_comp ? req_comp : *comp; - int row,col,z; - stbi_uc *image = (stbi_uc *) result; - - // @OPTIMIZE: use a bigger temp buffer and memcpy multiple pixels at once - for (row = 0; row < (h>>1); row++) { - for (col = 0; col < w; col++) { - for (z = 0; z < channels; z++) { - stbi_uc temp = image[(row * w + col) * channels + z]; - image[(row * w + col) * channels + z] = image[((h - row - 1) * w + col) * channels + z]; - image[((h - row - 1) * w + col) * channels + z] = temp; - } - } - } - } - - return (unsigned char *) result; -} - -static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - stbi__result_info ri; - void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16); - - if (result == NULL) - return NULL; - - if (ri.bits_per_channel != 16) { - STBI_ASSERT(ri.bits_per_channel == 8); - result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp); - ri.bits_per_channel = 16; - } - - // @TODO: move stbi__convert_format16 to here - // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision - - if (stbi__vertically_flip_on_load) { - int w = *x, h = *y; - int channels = req_comp ? req_comp : *comp; - int row,col,z; - stbi__uint16 *image = (stbi__uint16 *) result; - - // @OPTIMIZE: use a bigger temp buffer and memcpy multiple pixels at once - for (row = 0; row < (h>>1); row++) { - for (col = 0; col < w; col++) { - for (z = 0; z < channels; z++) { - stbi__uint16 temp = image[(row * w + col) * channels + z]; - image[(row * w + col) * channels + z] = image[((h - row - 1) * w + col) * channels + z]; - image[((h - row - 1) * w + col) * channels + z] = temp; - } - } - } - } - - return (stbi__uint16 *) result; -} - -#ifndef STBI_NO_HDR -static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) -{ - if (stbi__vertically_flip_on_load && result != NULL) { - int w = *x, h = *y; - int depth = req_comp ? req_comp : *comp; - int row,col,z; - float temp; - - // @OPTIMIZE: use a bigger temp buffer and memcpy multiple pixels at once - for (row = 0; row < (h>>1); row++) { - for (col = 0; col < w; col++) { - for (z = 0; z < depth; z++) { - temp = result[(row * w + col) * depth + z]; - result[(row * w + col) * depth + z] = result[((h - row - 1) * w + col) * depth + z]; - result[((h - row - 1) * w + col) * depth + z] = temp; - } - } - } - } -} -#endif - -#ifndef STBI_NO_STDIO - -static FILE *stbi__fopen(char const *filename, char const *mode) -{ - FILE *f; -#if defined(_MSC_VER) && _MSC_VER >= 1400 - if (0 != fopen_s(&f, filename, mode)) - f=0; -#else - f = fopen(filename, mode); -#endif - return f; -} - - -STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) -{ - FILE *f = stbi__fopen(filename, "rb"); - unsigned char *result; - if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); - result = stbi_load_from_file(f,x,y,comp,req_comp); - fclose(f); - return result; -} - -STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) -{ - unsigned char *result; - stbi__context s; - stbi__start_file(&s,f); - result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); - if (result) { - // need to 'unget' all the characters in the IO buffer - fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); - } - return result; -} - -STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp) -{ - stbi__uint16 *result; - stbi__context s; - stbi__start_file(&s,f); - result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp); - if (result) { - // need to 'unget' all the characters in the IO buffer - fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); - } - return result; -} - -STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp) -{ - FILE *f = stbi__fopen(filename, "rb"); - stbi__uint16 *result; - if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file"); - result = stbi_load_from_file_16(f,x,y,comp,req_comp); - fclose(f); - return result; -} - - -#endif //!STBI_NO_STDIO - -STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); -} - -STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); -} - -#ifndef STBI_NO_LINEAR -static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - unsigned char *data; - #ifndef STBI_NO_HDR - if (stbi__hdr_test(s)) { - stbi__result_info ri; - float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri); - if (hdr_data) - stbi__float_postprocess(hdr_data,x,y,comp,req_comp); - return hdr_data; - } - #endif - data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp); - if (data) - return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); - return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); -} - -STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__loadf_main(&s,x,y,comp,req_comp); -} - -STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi__loadf_main(&s,x,y,comp,req_comp); -} - -#ifndef STBI_NO_STDIO -STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) -{ - float *result; - FILE *f = stbi__fopen(filename, "rb"); - if (!f) return stbi__errpf("can't fopen", "Unable to open file"); - result = stbi_loadf_from_file(f,x,y,comp,req_comp); - fclose(f); - return result; -} - -STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_file(&s,f); - return stbi__loadf_main(&s,x,y,comp,req_comp); -} -#endif // !STBI_NO_STDIO - -#endif // !STBI_NO_LINEAR - -// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is -// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always -// reports false! - -STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) -{ - #ifndef STBI_NO_HDR - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__hdr_test(&s); - #else - STBI_NOTUSED(buffer); - STBI_NOTUSED(len); - return 0; - #endif -} - -#ifndef STBI_NO_STDIO -STBIDEF int stbi_is_hdr (char const *filename) -{ - FILE *f = stbi__fopen(filename, "rb"); - int result=0; - if (f) { - result = stbi_is_hdr_from_file(f); - fclose(f); - } - return result; -} - -STBIDEF int stbi_is_hdr_from_file(FILE *f) -{ - #ifndef STBI_NO_HDR - stbi__context s; - stbi__start_file(&s,f); - return stbi__hdr_test(&s); - #else - STBI_NOTUSED(f); - return 0; - #endif -} -#endif // !STBI_NO_STDIO - -STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) -{ - #ifndef STBI_NO_HDR - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi__hdr_test(&s); - #else - STBI_NOTUSED(clbk); - STBI_NOTUSED(user); - return 0; - #endif -} - -#ifndef STBI_NO_LINEAR -static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; - -STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } -STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } -#endif - -static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; - -STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } -STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } - - -////////////////////////////////////////////////////////////////////////////// -// -// Common code used by all image loaders -// - -enum -{ - STBI__SCAN_load=0, - STBI__SCAN_type, - STBI__SCAN_header -}; - -static void stbi__refill_buffer(stbi__context *s) -{ - int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); - if (n == 0) { - // at end of file, treat same as if from memory, but need to handle case - // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file - s->read_from_callbacks = 0; - s->img_buffer = s->buffer_start; - s->img_buffer_end = s->buffer_start+1; - *s->img_buffer = 0; - } else { - s->img_buffer = s->buffer_start; - s->img_buffer_end = s->buffer_start + n; - } -} - -stbi_inline static stbi_uc stbi__get8(stbi__context *s) -{ - if (s->img_buffer < s->img_buffer_end) - return *s->img_buffer++; - if (s->read_from_callbacks) { - stbi__refill_buffer(s); - return *s->img_buffer++; - } - return 0; -} - -stbi_inline static int stbi__at_eof(stbi__context *s) -{ - if (s->io.read) { - if (!(s->io.eof)(s->io_user_data)) return 0; - // if feof() is true, check if buffer = end - // special case: we've only got the special 0 character at the end - if (s->read_from_callbacks == 0) return 1; - } - - return s->img_buffer >= s->img_buffer_end; -} - -static void stbi__skip(stbi__context *s, int n) -{ - if (n < 0) { - s->img_buffer = s->img_buffer_end; - return; - } - if (s->io.read) { - int blen = (int) (s->img_buffer_end - s->img_buffer); - if (blen < n) { - s->img_buffer = s->img_buffer_end; - (s->io.skip)(s->io_user_data, n - blen); - return; - } - } - s->img_buffer += n; -} - -static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) -{ - if (s->io.read) { - int blen = (int) (s->img_buffer_end - s->img_buffer); - if (blen < n) { - int res, count; - - memcpy(buffer, s->img_buffer, blen); - - count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); - res = (count == (n-blen)); - s->img_buffer = s->img_buffer_end; - return res; - } - } - - if (s->img_buffer+n <= s->img_buffer_end) { - memcpy(buffer, s->img_buffer, n); - s->img_buffer += n; - return 1; - } else - return 0; -} - -static int stbi__get16be(stbi__context *s) -{ - int z = stbi__get8(s); - return (z << 8) + stbi__get8(s); -} - -static stbi__uint32 stbi__get32be(stbi__context *s) -{ - stbi__uint32 z = stbi__get16be(s); - return (z << 16) + stbi__get16be(s); -} - -#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) -// nothing -#else -static int stbi__get16le(stbi__context *s) -{ - int z = stbi__get8(s); - return z + (stbi__get8(s) << 8); -} -#endif - -#ifndef STBI_NO_BMP -static stbi__uint32 stbi__get32le(stbi__context *s) -{ - stbi__uint32 z = stbi__get16le(s); - return z + (stbi__get16le(s) << 16); -} -#endif - -#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings - - -////////////////////////////////////////////////////////////////////////////// -// -// generic converter from built-in img_n to req_comp -// individual types do this automatically as much as possible (e.g. jpeg -// does all cases internally since it needs to colorspace convert anyway, -// and it never has alpha, so very few cases ). png can automatically -// interleave an alpha=255 channel, but falls back to this for other cases -// -// assume data buffer is malloced, so malloc a new one and free that one -// only failure mode is malloc failing - -static stbi_uc stbi__compute_y(int r, int g, int b) -{ - return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); -} - -static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) -{ - int i,j; - unsigned char *good; - - if (req_comp == img_n) return data; - STBI_ASSERT(req_comp >= 1 && req_comp <= 4); - - good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0); - if (good == NULL) { - STBI_FREE(data); - return stbi__errpuc("outofmem", "Out of memory"); - } - - for (j=0; j < (int) y; ++j) { - unsigned char *src = data + j * x * img_n ; - unsigned char *dest = good + j * x * req_comp; - - #define STBI__COMBO(a,b) ((a)*8+(b)) - #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) - // convert source image with img_n components to one with req_comp components; - // avoid switch per pixel, so use switch per scanline and massive macros - switch (STBI__COMBO(img_n, req_comp)) { - STBI__CASE(1,2) { dest[0]=src[0], dest[1]=255; } break; - STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; - STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=255; } break; - STBI__CASE(2,1) { dest[0]=src[0]; } break; - STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; - STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; } break; - STBI__CASE(3,4) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=255; } break; - STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; - STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = 255; } break; - STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; - STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = src[3]; } break; - STBI__CASE(4,3) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; } break; - default: STBI_ASSERT(0); - } - #undef STBI__CASE - } - - STBI_FREE(data); - return good; -} - -static stbi__uint16 stbi__compute_y_16(int r, int g, int b) -{ - return (stbi__uint16) (((r*77) + (g*150) + (29*b)) >> 8); -} - -static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y) -{ - int i,j; - stbi__uint16 *good; - - if (req_comp == img_n) return data; - STBI_ASSERT(req_comp >= 1 && req_comp <= 4); - - good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2); - if (good == NULL) { - STBI_FREE(data); - return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); - } - - for (j=0; j < (int) y; ++j) { - stbi__uint16 *src = data + j * x * img_n ; - stbi__uint16 *dest = good + j * x * req_comp; - - #define STBI__COMBO(a,b) ((a)*8+(b)) - #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) - // convert source image with img_n components to one with req_comp components; - // avoid switch per pixel, so use switch per scanline and massive macros - switch (STBI__COMBO(img_n, req_comp)) { - STBI__CASE(1,2) { dest[0]=src[0], dest[1]=0xffff; } break; - STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; - STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=0xffff; } break; - STBI__CASE(2,1) { dest[0]=src[0]; } break; - STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; - STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; } break; - STBI__CASE(3,4) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=0xffff; } break; - STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; - STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]), dest[1] = 0xffff; } break; - STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; - STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]), dest[1] = src[3]; } break; - STBI__CASE(4,3) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; } break; - default: STBI_ASSERT(0); - } - #undef STBI__CASE - } - - STBI_FREE(data); - return good; -} - -#ifndef STBI_NO_LINEAR -static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) -{ - int i,k,n; - float *output; - if (!data) return NULL; - output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0); - if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } - // compute number of non-alpha components - if (comp & 1) n = comp; else n = comp-1; - for (i=0; i < x*y; ++i) { - for (k=0; k < n; ++k) { - output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); - } - if (k < comp) output[i*comp + k] = data[i*comp+k]/255.0f; - } - STBI_FREE(data); - return output; -} -#endif - -#ifndef STBI_NO_HDR -#define stbi__float2int(x) ((int) (x)) -static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) -{ - int i,k,n; - stbi_uc *output; - if (!data) return NULL; - output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0); - if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } - // compute number of non-alpha components - if (comp & 1) n = comp; else n = comp-1; - for (i=0; i < x*y; ++i) { - for (k=0; k < n; ++k) { - float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; - if (z < 0) z = 0; - if (z > 255) z = 255; - output[i*comp + k] = (stbi_uc) stbi__float2int(z); - } - if (k < comp) { - float z = data[i*comp+k] * 255 + 0.5f; - if (z < 0) z = 0; - if (z > 255) z = 255; - output[i*comp + k] = (stbi_uc) stbi__float2int(z); - } - } - STBI_FREE(data); - return output; -} -#endif - -////////////////////////////////////////////////////////////////////////////// -// -// "baseline" JPEG/JFIF decoder -// -// simple implementation -// - doesn't support delayed output of y-dimension -// - simple interface (only one output format: 8-bit interleaved RGB) -// - doesn't try to recover corrupt jpegs -// - doesn't allow partial loading, loading multiple at once -// - still fast on x86 (copying globals into locals doesn't help x86) -// - allocates lots of intermediate memory (full size of all components) -// - non-interleaved case requires this anyway -// - allows good upsampling (see next) -// high-quality -// - upsampled channels are bilinearly interpolated, even across blocks -// - quality integer IDCT derived from IJG's 'slow' -// performance -// - fast huffman; reasonable integer IDCT -// - some SIMD kernels for common paths on targets with SSE2/NEON -// - uses a lot of intermediate memory, could cache poorly - -#ifndef STBI_NO_JPEG - -// huffman decoding acceleration -#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache - -typedef struct -{ - stbi_uc fast[1 << FAST_BITS]; - // weirdly, repacking this into AoS is a 10% speed loss, instead of a win - stbi__uint16 code[256]; - stbi_uc values[256]; - stbi_uc size[257]; - unsigned int maxcode[18]; - int delta[17]; // old 'firstsymbol' - old 'firstcode' -} stbi__huffman; - -typedef struct -{ - stbi__context *s; - stbi__huffman huff_dc[4]; - stbi__huffman huff_ac[4]; - stbi__uint16 dequant[4][64]; - stbi__int16 fast_ac[4][1 << FAST_BITS]; - -// sizes for components, interleaved MCUs - int img_h_max, img_v_max; - int img_mcu_x, img_mcu_y; - int img_mcu_w, img_mcu_h; - -// definition of jpeg image component - struct - { - int id; - int h,v; - int tq; - int hd,ha; - int dc_pred; - - int x,y,w2,h2; - stbi_uc *data; - void *raw_data, *raw_coeff; - stbi_uc *linebuf; - short *coeff; // progressive only - int coeff_w, coeff_h; // number of 8x8 coefficient blocks - } img_comp[4]; - - stbi__uint32 code_buffer; // jpeg entropy-coded buffer - int code_bits; // number of valid bits - unsigned char marker; // marker seen while filling entropy buffer - int nomore; // flag if we saw a marker so must stop - - int progressive; - int spec_start; - int spec_end; - int succ_high; - int succ_low; - int eob_run; - int jfif; - int app14_color_transform; // Adobe APP14 tag - int rgb; - - int scan_n, order[4]; - int restart_interval, todo; - -// kernels - void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); - void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); - stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); -} stbi__jpeg; - -static int stbi__build_huffman(stbi__huffman *h, int *count) -{ - int i,j,k=0,code; - // build size list for each symbol (from JPEG spec) - for (i=0; i < 16; ++i) - for (j=0; j < count[i]; ++j) - h->size[k++] = (stbi_uc) (i+1); - h->size[k] = 0; - - // compute actual symbols (from jpeg spec) - code = 0; - k = 0; - for(j=1; j <= 16; ++j) { - // compute delta to add to code to compute symbol id - h->delta[j] = k - code; - if (h->size[k] == j) { - while (h->size[k] == j) - h->code[k++] = (stbi__uint16) (code++); - if (code-1 >= (1 << j)) return stbi__err("bad code lengths","Corrupt JPEG"); - } - // compute largest code + 1 for this size, preshifted as needed later - h->maxcode[j] = code << (16-j); - code <<= 1; - } - h->maxcode[j] = 0xffffffff; - - // build non-spec acceleration table; 255 is flag for not-accelerated - memset(h->fast, 255, 1 << FAST_BITS); - for (i=0; i < k; ++i) { - int s = h->size[i]; - if (s <= FAST_BITS) { - int c = h->code[i] << (FAST_BITS-s); - int m = 1 << (FAST_BITS-s); - for (j=0; j < m; ++j) { - h->fast[c+j] = (stbi_uc) i; - } - } - } - return 1; -} - -// build a table that decodes both magnitude and value of small ACs in -// one go. -static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) -{ - int i; - for (i=0; i < (1 << FAST_BITS); ++i) { - stbi_uc fast = h->fast[i]; - fast_ac[i] = 0; - if (fast < 255) { - int rs = h->values[fast]; - int run = (rs >> 4) & 15; - int magbits = rs & 15; - int len = h->size[fast]; - - if (magbits && len + magbits <= FAST_BITS) { - // magnitude code followed by receive_extend code - int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); - int m = 1 << (magbits - 1); - if (k < m) k += (~0U << magbits) + 1; - // if the result is small enough, we can fit it in fast_ac table - if (k >= -128 && k <= 127) - fast_ac[i] = (stbi__int16) ((k << 8) + (run << 4) + (len + magbits)); - } - } - } -} - -static void stbi__grow_buffer_unsafe(stbi__jpeg *j) -{ - do { - int b = j->nomore ? 0 : stbi__get8(j->s); - if (b == 0xff) { - int c = stbi__get8(j->s); - while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes - if (c != 0) { - j->marker = (unsigned char) c; - j->nomore = 1; - return; - } - } - j->code_buffer |= b << (24 - j->code_bits); - j->code_bits += 8; - } while (j->code_bits <= 24); -} - -// (1 << n) - 1 -static stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; - -// decode a jpeg huffman value from the bitstream -stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) -{ - unsigned int temp; - int c,k; - - if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); - - // look at the top FAST_BITS and determine what symbol ID it is, - // if the code is <= FAST_BITS - c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); - k = h->fast[c]; - if (k < 255) { - int s = h->size[k]; - if (s > j->code_bits) - return -1; - j->code_buffer <<= s; - j->code_bits -= s; - return h->values[k]; - } - - // naive test is to shift the code_buffer down so k bits are - // valid, then test against maxcode. To speed this up, we've - // preshifted maxcode left so that it has (16-k) 0s at the - // end; in other words, regardless of the number of bits, it - // wants to be compared against something shifted to have 16; - // that way we don't need to shift inside the loop. - temp = j->code_buffer >> 16; - for (k=FAST_BITS+1 ; ; ++k) - if (temp < h->maxcode[k]) - break; - if (k == 17) { - // error! code not found - j->code_bits -= 16; - return -1; - } - - if (k > j->code_bits) - return -1; - - // convert the huffman code to the symbol id - c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; - STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); - - // convert the id to a symbol - j->code_bits -= k; - j->code_buffer <<= k; - return h->values[c]; -} - -// bias[n] = (-1<code_bits < n) stbi__grow_buffer_unsafe(j); - - sgn = (stbi__int32)j->code_buffer >> 31; // sign bit is always in MSB - k = stbi_lrot(j->code_buffer, n); - STBI_ASSERT(n >= 0 && n < (int) (sizeof(stbi__bmask)/sizeof(*stbi__bmask))); - j->code_buffer = k & ~stbi__bmask[n]; - k &= stbi__bmask[n]; - j->code_bits -= n; - return k + (stbi__jbias[n] & ~sgn); -} - -// get some unsigned bits -stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) -{ - unsigned int k; - if (j->code_bits < n) stbi__grow_buffer_unsafe(j); - k = stbi_lrot(j->code_buffer, n); - j->code_buffer = k & ~stbi__bmask[n]; - k &= stbi__bmask[n]; - j->code_bits -= n; - return k; -} - -stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) -{ - unsigned int k; - if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); - k = j->code_buffer; - j->code_buffer <<= 1; - --j->code_bits; - return k & 0x80000000; -} - -// given a value that's at position X in the zigzag stream, -// where does it appear in the 8x8 matrix coded as row-major? -static stbi_uc stbi__jpeg_dezigzag[64+15] = -{ - 0, 1, 8, 16, 9, 2, 3, 10, - 17, 24, 32, 25, 18, 11, 4, 5, - 12, 19, 26, 33, 40, 48, 41, 34, - 27, 20, 13, 6, 7, 14, 21, 28, - 35, 42, 49, 56, 57, 50, 43, 36, - 29, 22, 15, 23, 30, 37, 44, 51, - 58, 59, 52, 45, 38, 31, 39, 46, - 53, 60, 61, 54, 47, 55, 62, 63, - // let corrupt input sample past end - 63, 63, 63, 63, 63, 63, 63, 63, - 63, 63, 63, 63, 63, 63, 63 -}; - -// decode one 64-entry block-- -static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi__uint16 *dequant) -{ - int diff,dc,k; - int t; - - if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); - t = stbi__jpeg_huff_decode(j, hdc); - if (t < 0) return stbi__err("bad huffman code","Corrupt JPEG"); - - // 0 all the ac values now so we can do it 32-bits at a time - memset(data,0,64*sizeof(data[0])); - - diff = t ? stbi__extend_receive(j, t) : 0; - dc = j->img_comp[b].dc_pred + diff; - j->img_comp[b].dc_pred = dc; - data[0] = (short) (dc * dequant[0]); - - // decode AC components, see JPEG spec - k = 1; - do { - unsigned int zig; - int c,r,s; - if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); - c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); - r = fac[c]; - if (r) { // fast-AC path - k += (r >> 4) & 15; // run - s = r & 15; // combined length - j->code_buffer <<= s; - j->code_bits -= s; - // decode into unzigzag'd location - zig = stbi__jpeg_dezigzag[k++]; - data[zig] = (short) ((r >> 8) * dequant[zig]); - } else { - int rs = stbi__jpeg_huff_decode(j, hac); - if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); - s = rs & 15; - r = rs >> 4; - if (s == 0) { - if (rs != 0xf0) break; // end block - k += 16; - } else { - k += r; - // decode into unzigzag'd location - zig = stbi__jpeg_dezigzag[k++]; - data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); - } - } - } while (k < 64); - return 1; -} - -static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) -{ - int diff,dc; - int t; - if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); - - if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); - - if (j->succ_high == 0) { - // first scan for DC coefficient, must be first - memset(data,0,64*sizeof(data[0])); // 0 all the ac values now - t = stbi__jpeg_huff_decode(j, hdc); - diff = t ? stbi__extend_receive(j, t) : 0; - - dc = j->img_comp[b].dc_pred + diff; - j->img_comp[b].dc_pred = dc; - data[0] = (short) (dc << j->succ_low); - } else { - // refinement scan for DC coefficient - if (stbi__jpeg_get_bit(j)) - data[0] += (short) (1 << j->succ_low); - } - return 1; -} - -// @OPTIMIZE: store non-zigzagged during the decode passes, -// and only de-zigzag when dequantizing -static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) -{ - int k; - if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); - - if (j->succ_high == 0) { - int shift = j->succ_low; - - if (j->eob_run) { - --j->eob_run; - return 1; - } - - k = j->spec_start; - do { - unsigned int zig; - int c,r,s; - if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); - c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); - r = fac[c]; - if (r) { // fast-AC path - k += (r >> 4) & 15; // run - s = r & 15; // combined length - j->code_buffer <<= s; - j->code_bits -= s; - zig = stbi__jpeg_dezigzag[k++]; - data[zig] = (short) ((r >> 8) << shift); - } else { - int rs = stbi__jpeg_huff_decode(j, hac); - if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); - s = rs & 15; - r = rs >> 4; - if (s == 0) { - if (r < 15) { - j->eob_run = (1 << r); - if (r) - j->eob_run += stbi__jpeg_get_bits(j, r); - --j->eob_run; - break; - } - k += 16; - } else { - k += r; - zig = stbi__jpeg_dezigzag[k++]; - data[zig] = (short) (stbi__extend_receive(j,s) << shift); - } - } - } while (k <= j->spec_end); - } else { - // refinement scan for these AC coefficients - - short bit = (short) (1 << j->succ_low); - - if (j->eob_run) { - --j->eob_run; - for (k = j->spec_start; k <= j->spec_end; ++k) { - short *p = &data[stbi__jpeg_dezigzag[k]]; - if (*p != 0) - if (stbi__jpeg_get_bit(j)) - if ((*p & bit)==0) { - if (*p > 0) - *p += bit; - else - *p -= bit; - } - } - } else { - k = j->spec_start; - do { - int r,s; - int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh - if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); - s = rs & 15; - r = rs >> 4; - if (s == 0) { - if (r < 15) { - j->eob_run = (1 << r) - 1; - if (r) - j->eob_run += stbi__jpeg_get_bits(j, r); - r = 64; // force end of block - } else { - // r=15 s=0 should write 16 0s, so we just do - // a run of 15 0s and then write s (which is 0), - // so we don't have to do anything special here - } - } else { - if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); - // sign bit - if (stbi__jpeg_get_bit(j)) - s = bit; - else - s = -bit; - } - - // advance by r - while (k <= j->spec_end) { - short *p = &data[stbi__jpeg_dezigzag[k++]]; - if (*p != 0) { - if (stbi__jpeg_get_bit(j)) - if ((*p & bit)==0) { - if (*p > 0) - *p += bit; - else - *p -= bit; - } - } else { - if (r == 0) { - *p = (short) s; - break; - } - --r; - } - } - } while (k <= j->spec_end); - } - } - return 1; -} - -// take a -128..127 value and stbi__clamp it and convert to 0..255 -stbi_inline static stbi_uc stbi__clamp(int x) -{ - // trick to use a single test to catch both cases - if ((unsigned int) x > 255) { - if (x < 0) return 0; - if (x > 255) return 255; - } - return (stbi_uc) x; -} - -#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) -#define stbi__fsh(x) ((x) << 12) - -// derived from jidctint -- DCT_ISLOW -#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ - int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ - p2 = s2; \ - p3 = s6; \ - p1 = (p2+p3) * stbi__f2f(0.5411961f); \ - t2 = p1 + p3*stbi__f2f(-1.847759065f); \ - t3 = p1 + p2*stbi__f2f( 0.765366865f); \ - p2 = s0; \ - p3 = s4; \ - t0 = stbi__fsh(p2+p3); \ - t1 = stbi__fsh(p2-p3); \ - x0 = t0+t3; \ - x3 = t0-t3; \ - x1 = t1+t2; \ - x2 = t1-t2; \ - t0 = s7; \ - t1 = s5; \ - t2 = s3; \ - t3 = s1; \ - p3 = t0+t2; \ - p4 = t1+t3; \ - p1 = t0+t3; \ - p2 = t1+t2; \ - p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ - t0 = t0*stbi__f2f( 0.298631336f); \ - t1 = t1*stbi__f2f( 2.053119869f); \ - t2 = t2*stbi__f2f( 3.072711026f); \ - t3 = t3*stbi__f2f( 1.501321110f); \ - p1 = p5 + p1*stbi__f2f(-0.899976223f); \ - p2 = p5 + p2*stbi__f2f(-2.562915447f); \ - p3 = p3*stbi__f2f(-1.961570560f); \ - p4 = p4*stbi__f2f(-0.390180644f); \ - t3 += p1+p4; \ - t2 += p2+p3; \ - t1 += p2+p4; \ - t0 += p1+p3; - -static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) -{ - int i,val[64],*v=val; - stbi_uc *o; - short *d = data; - - // columns - for (i=0; i < 8; ++i,++d, ++v) { - // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing - if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 - && d[40]==0 && d[48]==0 && d[56]==0) { - // no shortcut 0 seconds - // (1|2|3|4|5|6|7)==0 0 seconds - // all separate -0.047 seconds - // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds - int dcterm = d[0] << 2; - v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; - } else { - STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) - // constants scaled things up by 1<<12; let's bring them back - // down, but keep 2 extra bits of precision - x0 += 512; x1 += 512; x2 += 512; x3 += 512; - v[ 0] = (x0+t3) >> 10; - v[56] = (x0-t3) >> 10; - v[ 8] = (x1+t2) >> 10; - v[48] = (x1-t2) >> 10; - v[16] = (x2+t1) >> 10; - v[40] = (x2-t1) >> 10; - v[24] = (x3+t0) >> 10; - v[32] = (x3-t0) >> 10; - } - } - - for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { - // no fast case since the first 1D IDCT spread components out - STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) - // constants scaled things up by 1<<12, plus we had 1<<2 from first - // loop, plus horizontal and vertical each scale by sqrt(8) so together - // we've got an extra 1<<3, so 1<<17 total we need to remove. - // so we want to round that, which means adding 0.5 * 1<<17, - // aka 65536. Also, we'll end up with -128 to 127 that we want - // to encode as 0..255 by adding 128, so we'll add that before the shift - x0 += 65536 + (128<<17); - x1 += 65536 + (128<<17); - x2 += 65536 + (128<<17); - x3 += 65536 + (128<<17); - // tried computing the shifts into temps, or'ing the temps to see - // if any were out of range, but that was slower - o[0] = stbi__clamp((x0+t3) >> 17); - o[7] = stbi__clamp((x0-t3) >> 17); - o[1] = stbi__clamp((x1+t2) >> 17); - o[6] = stbi__clamp((x1-t2) >> 17); - o[2] = stbi__clamp((x2+t1) >> 17); - o[5] = stbi__clamp((x2-t1) >> 17); - o[3] = stbi__clamp((x3+t0) >> 17); - o[4] = stbi__clamp((x3-t0) >> 17); - } -} - -#ifdef STBI_SSE2 -// sse2 integer IDCT. not the fastest possible implementation but it -// produces bit-identical results to the generic C version so it's -// fully "transparent". -static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) -{ - // This is constructed to match our regular (generic) integer IDCT exactly. - __m128i row0, row1, row2, row3, row4, row5, row6, row7; - __m128i tmp; - - // dot product constant: even elems=x, odd elems=y - #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) - - // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) - // out(1) = c1[even]*x + c1[odd]*y - #define dct_rot(out0,out1, x,y,c0,c1) \ - __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ - __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ - __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ - __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ - __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ - __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) - - // out = in << 12 (in 16-bit, out 32-bit) - #define dct_widen(out, in) \ - __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ - __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) - - // wide add - #define dct_wadd(out, a, b) \ - __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ - __m128i out##_h = _mm_add_epi32(a##_h, b##_h) - - // wide sub - #define dct_wsub(out, a, b) \ - __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ - __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) - - // butterfly a/b, add bias, then shift by "s" and pack - #define dct_bfly32o(out0, out1, a,b,bias,s) \ - { \ - __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ - __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ - dct_wadd(sum, abiased, b); \ - dct_wsub(dif, abiased, b); \ - out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ - out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ - } - - // 8-bit interleave step (for transposes) - #define dct_interleave8(a, b) \ - tmp = a; \ - a = _mm_unpacklo_epi8(a, b); \ - b = _mm_unpackhi_epi8(tmp, b) - - // 16-bit interleave step (for transposes) - #define dct_interleave16(a, b) \ - tmp = a; \ - a = _mm_unpacklo_epi16(a, b); \ - b = _mm_unpackhi_epi16(tmp, b) - - #define dct_pass(bias,shift) \ - { \ - /* even part */ \ - dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ - __m128i sum04 = _mm_add_epi16(row0, row4); \ - __m128i dif04 = _mm_sub_epi16(row0, row4); \ - dct_widen(t0e, sum04); \ - dct_widen(t1e, dif04); \ - dct_wadd(x0, t0e, t3e); \ - dct_wsub(x3, t0e, t3e); \ - dct_wadd(x1, t1e, t2e); \ - dct_wsub(x2, t1e, t2e); \ - /* odd part */ \ - dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ - dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ - __m128i sum17 = _mm_add_epi16(row1, row7); \ - __m128i sum35 = _mm_add_epi16(row3, row5); \ - dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ - dct_wadd(x4, y0o, y4o); \ - dct_wadd(x5, y1o, y5o); \ - dct_wadd(x6, y2o, y5o); \ - dct_wadd(x7, y3o, y4o); \ - dct_bfly32o(row0,row7, x0,x7,bias,shift); \ - dct_bfly32o(row1,row6, x1,x6,bias,shift); \ - dct_bfly32o(row2,row5, x2,x5,bias,shift); \ - dct_bfly32o(row3,row4, x3,x4,bias,shift); \ - } - - __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); - __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); - __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); - __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); - __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); - __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); - __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); - __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); - - // rounding biases in column/row passes, see stbi__idct_block for explanation. - __m128i bias_0 = _mm_set1_epi32(512); - __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); - - // load - row0 = _mm_load_si128((const __m128i *) (data + 0*8)); - row1 = _mm_load_si128((const __m128i *) (data + 1*8)); - row2 = _mm_load_si128((const __m128i *) (data + 2*8)); - row3 = _mm_load_si128((const __m128i *) (data + 3*8)); - row4 = _mm_load_si128((const __m128i *) (data + 4*8)); - row5 = _mm_load_si128((const __m128i *) (data + 5*8)); - row6 = _mm_load_si128((const __m128i *) (data + 6*8)); - row7 = _mm_load_si128((const __m128i *) (data + 7*8)); - - // column pass - dct_pass(bias_0, 10); - - { - // 16bit 8x8 transpose pass 1 - dct_interleave16(row0, row4); - dct_interleave16(row1, row5); - dct_interleave16(row2, row6); - dct_interleave16(row3, row7); - - // transpose pass 2 - dct_interleave16(row0, row2); - dct_interleave16(row1, row3); - dct_interleave16(row4, row6); - dct_interleave16(row5, row7); - - // transpose pass 3 - dct_interleave16(row0, row1); - dct_interleave16(row2, row3); - dct_interleave16(row4, row5); - dct_interleave16(row6, row7); - } - - // row pass - dct_pass(bias_1, 17); - - { - // pack - __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 - __m128i p1 = _mm_packus_epi16(row2, row3); - __m128i p2 = _mm_packus_epi16(row4, row5); - __m128i p3 = _mm_packus_epi16(row6, row7); - - // 8bit 8x8 transpose pass 1 - dct_interleave8(p0, p2); // a0e0a1e1... - dct_interleave8(p1, p3); // c0g0c1g1... - - // transpose pass 2 - dct_interleave8(p0, p1); // a0c0e0g0... - dct_interleave8(p2, p3); // b0d0f0h0... - - // transpose pass 3 - dct_interleave8(p0, p2); // a0b0c0d0... - dct_interleave8(p1, p3); // a4b4c4d4... - - // store - _mm_storel_epi64((__m128i *) out, p0); out += out_stride; - _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; - _mm_storel_epi64((__m128i *) out, p2); out += out_stride; - _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; - _mm_storel_epi64((__m128i *) out, p1); out += out_stride; - _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; - _mm_storel_epi64((__m128i *) out, p3); out += out_stride; - _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); - } - -#undef dct_const -#undef dct_rot -#undef dct_widen -#undef dct_wadd -#undef dct_wsub -#undef dct_bfly32o -#undef dct_interleave8 -#undef dct_interleave16 -#undef dct_pass -} - -#endif // STBI_SSE2 - -#ifdef STBI_NEON - -// NEON integer IDCT. should produce bit-identical -// results to the generic C version. -static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) -{ - int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; - - int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); - int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); - int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); - int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); - int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); - int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); - int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); - int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); - int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); - int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); - int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); - int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); - -#define dct_long_mul(out, inq, coeff) \ - int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ - int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) - -#define dct_long_mac(out, acc, inq, coeff) \ - int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ - int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) - -#define dct_widen(out, inq) \ - int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ - int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) - -// wide add -#define dct_wadd(out, a, b) \ - int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ - int32x4_t out##_h = vaddq_s32(a##_h, b##_h) - -// wide sub -#define dct_wsub(out, a, b) \ - int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ - int32x4_t out##_h = vsubq_s32(a##_h, b##_h) - -// butterfly a/b, then shift using "shiftop" by "s" and pack -#define dct_bfly32o(out0,out1, a,b,shiftop,s) \ - { \ - dct_wadd(sum, a, b); \ - dct_wsub(dif, a, b); \ - out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ - out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ - } - -#define dct_pass(shiftop, shift) \ - { \ - /* even part */ \ - int16x8_t sum26 = vaddq_s16(row2, row6); \ - dct_long_mul(p1e, sum26, rot0_0); \ - dct_long_mac(t2e, p1e, row6, rot0_1); \ - dct_long_mac(t3e, p1e, row2, rot0_2); \ - int16x8_t sum04 = vaddq_s16(row0, row4); \ - int16x8_t dif04 = vsubq_s16(row0, row4); \ - dct_widen(t0e, sum04); \ - dct_widen(t1e, dif04); \ - dct_wadd(x0, t0e, t3e); \ - dct_wsub(x3, t0e, t3e); \ - dct_wadd(x1, t1e, t2e); \ - dct_wsub(x2, t1e, t2e); \ - /* odd part */ \ - int16x8_t sum15 = vaddq_s16(row1, row5); \ - int16x8_t sum17 = vaddq_s16(row1, row7); \ - int16x8_t sum35 = vaddq_s16(row3, row5); \ - int16x8_t sum37 = vaddq_s16(row3, row7); \ - int16x8_t sumodd = vaddq_s16(sum17, sum35); \ - dct_long_mul(p5o, sumodd, rot1_0); \ - dct_long_mac(p1o, p5o, sum17, rot1_1); \ - dct_long_mac(p2o, p5o, sum35, rot1_2); \ - dct_long_mul(p3o, sum37, rot2_0); \ - dct_long_mul(p4o, sum15, rot2_1); \ - dct_wadd(sump13o, p1o, p3o); \ - dct_wadd(sump24o, p2o, p4o); \ - dct_wadd(sump23o, p2o, p3o); \ - dct_wadd(sump14o, p1o, p4o); \ - dct_long_mac(x4, sump13o, row7, rot3_0); \ - dct_long_mac(x5, sump24o, row5, rot3_1); \ - dct_long_mac(x6, sump23o, row3, rot3_2); \ - dct_long_mac(x7, sump14o, row1, rot3_3); \ - dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ - dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ - dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ - dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ - } - - // load - row0 = vld1q_s16(data + 0*8); - row1 = vld1q_s16(data + 1*8); - row2 = vld1q_s16(data + 2*8); - row3 = vld1q_s16(data + 3*8); - row4 = vld1q_s16(data + 4*8); - row5 = vld1q_s16(data + 5*8); - row6 = vld1q_s16(data + 6*8); - row7 = vld1q_s16(data + 7*8); - - // add DC bias - row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); - - // column pass - dct_pass(vrshrn_n_s32, 10); - - // 16bit 8x8 transpose - { -// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. -// whether compilers actually get this is another story, sadly. -#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } -#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } -#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } - - // pass 1 - dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 - dct_trn16(row2, row3); - dct_trn16(row4, row5); - dct_trn16(row6, row7); - - // pass 2 - dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 - dct_trn32(row1, row3); - dct_trn32(row4, row6); - dct_trn32(row5, row7); - - // pass 3 - dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 - dct_trn64(row1, row5); - dct_trn64(row2, row6); - dct_trn64(row3, row7); - -#undef dct_trn16 -#undef dct_trn32 -#undef dct_trn64 - } - - // row pass - // vrshrn_n_s32 only supports shifts up to 16, we need - // 17. so do a non-rounding shift of 16 first then follow - // up with a rounding shift by 1. - dct_pass(vshrn_n_s32, 16); - - { - // pack and round - uint8x8_t p0 = vqrshrun_n_s16(row0, 1); - uint8x8_t p1 = vqrshrun_n_s16(row1, 1); - uint8x8_t p2 = vqrshrun_n_s16(row2, 1); - uint8x8_t p3 = vqrshrun_n_s16(row3, 1); - uint8x8_t p4 = vqrshrun_n_s16(row4, 1); - uint8x8_t p5 = vqrshrun_n_s16(row5, 1); - uint8x8_t p6 = vqrshrun_n_s16(row6, 1); - uint8x8_t p7 = vqrshrun_n_s16(row7, 1); - - // again, these can translate into one instruction, but often don't. -#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } -#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } -#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } - - // sadly can't use interleaved stores here since we only write - // 8 bytes to each scan line! - - // 8x8 8-bit transpose pass 1 - dct_trn8_8(p0, p1); - dct_trn8_8(p2, p3); - dct_trn8_8(p4, p5); - dct_trn8_8(p6, p7); - - // pass 2 - dct_trn8_16(p0, p2); - dct_trn8_16(p1, p3); - dct_trn8_16(p4, p6); - dct_trn8_16(p5, p7); - - // pass 3 - dct_trn8_32(p0, p4); - dct_trn8_32(p1, p5); - dct_trn8_32(p2, p6); - dct_trn8_32(p3, p7); - - // store - vst1_u8(out, p0); out += out_stride; - vst1_u8(out, p1); out += out_stride; - vst1_u8(out, p2); out += out_stride; - vst1_u8(out, p3); out += out_stride; - vst1_u8(out, p4); out += out_stride; - vst1_u8(out, p5); out += out_stride; - vst1_u8(out, p6); out += out_stride; - vst1_u8(out, p7); - -#undef dct_trn8_8 -#undef dct_trn8_16 -#undef dct_trn8_32 - } - -#undef dct_long_mul -#undef dct_long_mac -#undef dct_widen -#undef dct_wadd -#undef dct_wsub -#undef dct_bfly32o -#undef dct_pass -} - -#endif // STBI_NEON - -#define STBI__MARKER_none 0xff -// if there's a pending marker from the entropy stream, return that -// otherwise, fetch from the stream and get a marker. if there's no -// marker, return 0xff, which is never a valid marker value -static stbi_uc stbi__get_marker(stbi__jpeg *j) -{ - stbi_uc x; - if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } - x = stbi__get8(j->s); - if (x != 0xff) return STBI__MARKER_none; - while (x == 0xff) - x = stbi__get8(j->s); // consume repeated 0xff fill bytes - return x; -} - -// in each scan, we'll have scan_n components, and the order -// of the components is specified by order[] -#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) - -// after a restart interval, stbi__jpeg_reset the entropy decoder and -// the dc prediction -static void stbi__jpeg_reset(stbi__jpeg *j) -{ - j->code_bits = 0; - j->code_buffer = 0; - j->nomore = 0; - j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = j->img_comp[3].dc_pred = 0; - j->marker = STBI__MARKER_none; - j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; - j->eob_run = 0; - // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, - // since we don't even allow 1<<30 pixels -} - -static int stbi__parse_entropy_coded_data(stbi__jpeg *z) -{ - stbi__jpeg_reset(z); - if (!z->progressive) { - if (z->scan_n == 1) { - int i,j; - STBI_SIMD_ALIGN(short, data[64]); - int n = z->order[0]; - // non-interleaved data, we just need to process one block at a time, - // in trivial scanline order - // number of blocks to do just depends on how many actual "pixels" this - // component has, independent of interleaved MCU blocking and such - int w = (z->img_comp[n].x+7) >> 3; - int h = (z->img_comp[n].y+7) >> 3; - for (j=0; j < h; ++j) { - for (i=0; i < w; ++i) { - int ha = z->img_comp[n].ha; - if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; - z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); - // every data block is an MCU, so countdown the restart interval - if (--z->todo <= 0) { - if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); - // if it's NOT a restart, then just bail, so we get corrupt data - // rather than no data - if (!STBI__RESTART(z->marker)) return 1; - stbi__jpeg_reset(z); - } - } - } - return 1; - } else { // interleaved - int i,j,k,x,y; - STBI_SIMD_ALIGN(short, data[64]); - for (j=0; j < z->img_mcu_y; ++j) { - for (i=0; i < z->img_mcu_x; ++i) { - // scan an interleaved mcu... process scan_n components in order - for (k=0; k < z->scan_n; ++k) { - int n = z->order[k]; - // scan out an mcu's worth of this component; that's just determined - // by the basic H and V specified for the component - for (y=0; y < z->img_comp[n].v; ++y) { - for (x=0; x < z->img_comp[n].h; ++x) { - int x2 = (i*z->img_comp[n].h + x)*8; - int y2 = (j*z->img_comp[n].v + y)*8; - int ha = z->img_comp[n].ha; - if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; - z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); - } - } - } - // after all interleaved components, that's an interleaved MCU, - // so now count down the restart interval - if (--z->todo <= 0) { - if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); - if (!STBI__RESTART(z->marker)) return 1; - stbi__jpeg_reset(z); - } - } - } - return 1; - } - } else { - if (z->scan_n == 1) { - int i,j; - int n = z->order[0]; - // non-interleaved data, we just need to process one block at a time, - // in trivial scanline order - // number of blocks to do just depends on how many actual "pixels" this - // component has, independent of interleaved MCU blocking and such - int w = (z->img_comp[n].x+7) >> 3; - int h = (z->img_comp[n].y+7) >> 3; - for (j=0; j < h; ++j) { - for (i=0; i < w; ++i) { - short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); - if (z->spec_start == 0) { - if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) - return 0; - } else { - int ha = z->img_comp[n].ha; - if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) - return 0; - } - // every data block is an MCU, so countdown the restart interval - if (--z->todo <= 0) { - if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); - if (!STBI__RESTART(z->marker)) return 1; - stbi__jpeg_reset(z); - } - } - } - return 1; - } else { // interleaved - int i,j,k,x,y; - for (j=0; j < z->img_mcu_y; ++j) { - for (i=0; i < z->img_mcu_x; ++i) { - // scan an interleaved mcu... process scan_n components in order - for (k=0; k < z->scan_n; ++k) { - int n = z->order[k]; - // scan out an mcu's worth of this component; that's just determined - // by the basic H and V specified for the component - for (y=0; y < z->img_comp[n].v; ++y) { - for (x=0; x < z->img_comp[n].h; ++x) { - int x2 = (i*z->img_comp[n].h + x); - int y2 = (j*z->img_comp[n].v + y); - short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); - if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) - return 0; - } - } - } - // after all interleaved components, that's an interleaved MCU, - // so now count down the restart interval - if (--z->todo <= 0) { - if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); - if (!STBI__RESTART(z->marker)) return 1; - stbi__jpeg_reset(z); - } - } - } - return 1; - } - } -} - -static void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant) -{ - int i; - for (i=0; i < 64; ++i) - data[i] *= dequant[i]; -} - -static void stbi__jpeg_finish(stbi__jpeg *z) -{ - if (z->progressive) { - // dequantize and idct the data - int i,j,n; - for (n=0; n < z->s->img_n; ++n) { - int w = (z->img_comp[n].x+7) >> 3; - int h = (z->img_comp[n].y+7) >> 3; - for (j=0; j < h; ++j) { - for (i=0; i < w; ++i) { - short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); - stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); - z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); - } - } - } - } -} - -static int stbi__process_marker(stbi__jpeg *z, int m) -{ - int L; - switch (m) { - case STBI__MARKER_none: // no marker found - return stbi__err("expected marker","Corrupt JPEG"); - - case 0xDD: // DRI - specify restart interval - if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); - z->restart_interval = stbi__get16be(z->s); - return 1; - - case 0xDB: // DQT - define quantization table - L = stbi__get16be(z->s)-2; - while (L > 0) { - int q = stbi__get8(z->s); - int p = q >> 4, sixteen = (p != 0); - int t = q & 15,i; - if (p != 0 && p != 1) return stbi__err("bad DQT type","Corrupt JPEG"); - if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); - - for (i=0; i < 64; ++i) - z->dequant[t][stbi__jpeg_dezigzag[i]] = sixteen ? stbi__get16be(z->s) : stbi__get8(z->s); - L -= (sixteen ? 129 : 65); - } - return L==0; - - case 0xC4: // DHT - define huffman table - L = stbi__get16be(z->s)-2; - while (L > 0) { - stbi_uc *v; - int sizes[16],i,n=0; - int q = stbi__get8(z->s); - int tc = q >> 4; - int th = q & 15; - if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); - for (i=0; i < 16; ++i) { - sizes[i] = stbi__get8(z->s); - n += sizes[i]; - } - L -= 17; - if (tc == 0) { - if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; - v = z->huff_dc[th].values; - } else { - if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; - v = z->huff_ac[th].values; - } - for (i=0; i < n; ++i) - v[i] = stbi__get8(z->s); - if (tc != 0) - stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); - L -= n; - } - return L==0; - } - - // check for comment block or APP blocks - if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { - L = stbi__get16be(z->s); - if (L < 2) { - if (m == 0xFE) - return stbi__err("bad COM len","Corrupt JPEG"); - else - return stbi__err("bad APP len","Corrupt JPEG"); - } - L -= 2; - - if (m == 0xE0 && L >= 5) { // JFIF APP0 segment - static const unsigned char tag[5] = {'J','F','I','F','\0'}; - int ok = 1; - int i; - for (i=0; i < 5; ++i) - if (stbi__get8(z->s) != tag[i]) - ok = 0; - L -= 5; - if (ok) - z->jfif = 1; - } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment - static const unsigned char tag[6] = {'A','d','o','b','e','\0'}; - int ok = 1; - int i; - for (i=0; i < 6; ++i) - if (stbi__get8(z->s) != tag[i]) - ok = 0; - L -= 6; - if (ok) { - stbi__get8(z->s); // version - stbi__get16be(z->s); // flags0 - stbi__get16be(z->s); // flags1 - z->app14_color_transform = stbi__get8(z->s); // color transform - L -= 6; - } - } - - stbi__skip(z->s, L); - return 1; - } - - return stbi__err("unknown marker","Corrupt JPEG"); -} - -// after we see SOS -static int stbi__process_scan_header(stbi__jpeg *z) -{ - int i; - int Ls = stbi__get16be(z->s); - z->scan_n = stbi__get8(z->s); - if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG"); - if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); - for (i=0; i < z->scan_n; ++i) { - int id = stbi__get8(z->s), which; - int q = stbi__get8(z->s); - for (which = 0; which < z->s->img_n; ++which) - if (z->img_comp[which].id == id) - break; - if (which == z->s->img_n) return 0; // no match - z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); - z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); - z->order[i] = which; - } - - { - int aa; - z->spec_start = stbi__get8(z->s); - z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 - aa = stbi__get8(z->s); - z->succ_high = (aa >> 4); - z->succ_low = (aa & 15); - if (z->progressive) { - if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) - return stbi__err("bad SOS", "Corrupt JPEG"); - } else { - if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); - if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); - z->spec_end = 63; - } - } - - return 1; -} - -static int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why) -{ - int i; - for (i=0; i < ncomp; ++i) { - if (z->img_comp[i].raw_data) { - STBI_FREE(z->img_comp[i].raw_data); - z->img_comp[i].raw_data = NULL; - z->img_comp[i].data = NULL; - } - if (z->img_comp[i].raw_coeff) { - STBI_FREE(z->img_comp[i].raw_coeff); - z->img_comp[i].raw_coeff = 0; - z->img_comp[i].coeff = 0; - } - if (z->img_comp[i].linebuf) { - STBI_FREE(z->img_comp[i].linebuf); - z->img_comp[i].linebuf = NULL; - } - } - return why; -} - -static int stbi__process_frame_header(stbi__jpeg *z, int scan) -{ - stbi__context *s = z->s; - int Lf,p,i,q, h_max=1,v_max=1,c; - Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG - p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline - s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG - s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires - c = stbi__get8(s); - if (c != 3 && c != 1 && c != 4) return stbi__err("bad component count","Corrupt JPEG"); - s->img_n = c; - for (i=0; i < c; ++i) { - z->img_comp[i].data = NULL; - z->img_comp[i].linebuf = NULL; - } - - if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); - - z->rgb = 0; - for (i=0; i < s->img_n; ++i) { - static unsigned char rgb[3] = { 'R', 'G', 'B' }; - z->img_comp[i].id = stbi__get8(s); - if (s->img_n == 3 && z->img_comp[i].id == rgb[i]) - ++z->rgb; - q = stbi__get8(s); - z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); - z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); - z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); - } - - if (scan != STBI__SCAN_load) return 1; - - if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err("too large", "Image too large to decode"); - - for (i=0; i < s->img_n; ++i) { - if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; - if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; - } - - // compute interleaved mcu info - z->img_h_max = h_max; - z->img_v_max = v_max; - z->img_mcu_w = h_max * 8; - z->img_mcu_h = v_max * 8; - // these sizes can't be more than 17 bits - z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; - z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; - - for (i=0; i < s->img_n; ++i) { - // number of effective pixels (e.g. for non-interleaved MCU) - z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; - z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; - // to simplify generation, we'll allocate enough memory to decode - // the bogus oversized data from using interleaved MCUs and their - // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't - // discard the extra data until colorspace conversion - // - // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier) - // so these muls can't overflow with 32-bit ints (which we require) - z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; - z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; - z->img_comp[i].coeff = 0; - z->img_comp[i].raw_coeff = 0; - z->img_comp[i].linebuf = NULL; - z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15); - if (z->img_comp[i].raw_data == NULL) - return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); - // align blocks for idct using mmx/sse - z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); - if (z->progressive) { - // w2, h2 are multiples of 8 (see above) - z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8; - z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8; - z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15); - if (z->img_comp[i].raw_coeff == NULL) - return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); - z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); - } - } - - return 1; -} - -// use comparisons since in some cases we handle more than one case (e.g. SOF) -#define stbi__DNL(x) ((x) == 0xdc) -#define stbi__SOI(x) ((x) == 0xd8) -#define stbi__EOI(x) ((x) == 0xd9) -#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) -#define stbi__SOS(x) ((x) == 0xda) - -#define stbi__SOF_progressive(x) ((x) == 0xc2) - -static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) -{ - int m; - z->jfif = 0; - z->app14_color_transform = -1; // valid values are 0,1,2 - z->marker = STBI__MARKER_none; // initialize cached marker to empty - m = stbi__get_marker(z); - if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); - if (scan == STBI__SCAN_type) return 1; - m = stbi__get_marker(z); - while (!stbi__SOF(m)) { - if (!stbi__process_marker(z,m)) return 0; - m = stbi__get_marker(z); - while (m == STBI__MARKER_none) { - // some files have extra padding after their blocks, so ok, we'll scan - if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); - m = stbi__get_marker(z); - } - } - z->progressive = stbi__SOF_progressive(m); - if (!stbi__process_frame_header(z, scan)) return 0; - return 1; -} - -// decode image to YCbCr format -static int stbi__decode_jpeg_image(stbi__jpeg *j) -{ - int m; - for (m = 0; m < 4; m++) { - j->img_comp[m].raw_data = NULL; - j->img_comp[m].raw_coeff = NULL; - } - j->restart_interval = 0; - if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; - m = stbi__get_marker(j); - while (!stbi__EOI(m)) { - if (stbi__SOS(m)) { - if (!stbi__process_scan_header(j)) return 0; - if (!stbi__parse_entropy_coded_data(j)) return 0; - if (j->marker == STBI__MARKER_none ) { - // handle 0s at the end of image data from IP Kamera 9060 - while (!stbi__at_eof(j->s)) { - int x = stbi__get8(j->s); - if (x == 255) { - j->marker = stbi__get8(j->s); - break; - } - } - // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 - } - } else if (stbi__DNL(m)) { - int Ld = stbi__get16be(j->s); - stbi__uint32 NL = stbi__get16be(j->s); - if (Ld != 4) stbi__err("bad DNL len", "Corrupt JPEG"); - if (NL != j->s->img_y) stbi__err("bad DNL height", "Corrupt JPEG"); - } else { - if (!stbi__process_marker(j, m)) return 0; - } - m = stbi__get_marker(j); - } - if (j->progressive) - stbi__jpeg_finish(j); - return 1; -} - -// static jfif-centered resampling (across block boundaries) - -typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, - int w, int hs); - -#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) - -static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - STBI_NOTUSED(out); - STBI_NOTUSED(in_far); - STBI_NOTUSED(w); - STBI_NOTUSED(hs); - return in_near; -} - -static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // need to generate two samples vertically for every one in input - int i; - STBI_NOTUSED(hs); - for (i=0; i < w; ++i) - out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); - return out; -} - -static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // need to generate two samples horizontally for every one in input - int i; - stbi_uc *input = in_near; - - if (w == 1) { - // if only one sample, can't do any interpolation - out[0] = out[1] = input[0]; - return out; - } - - out[0] = input[0]; - out[1] = stbi__div4(input[0]*3 + input[1] + 2); - for (i=1; i < w-1; ++i) { - int n = 3*input[i]+2; - out[i*2+0] = stbi__div4(n+input[i-1]); - out[i*2+1] = stbi__div4(n+input[i+1]); - } - out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); - out[i*2+1] = input[w-1]; - - STBI_NOTUSED(in_far); - STBI_NOTUSED(hs); - - return out; -} - -#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) - -static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // need to generate 2x2 samples for every one in input - int i,t0,t1; - if (w == 1) { - out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); - return out; - } - - t1 = 3*in_near[0] + in_far[0]; - out[0] = stbi__div4(t1+2); - for (i=1; i < w; ++i) { - t0 = t1; - t1 = 3*in_near[i]+in_far[i]; - out[i*2-1] = stbi__div16(3*t0 + t1 + 8); - out[i*2 ] = stbi__div16(3*t1 + t0 + 8); - } - out[w*2-1] = stbi__div4(t1+2); - - STBI_NOTUSED(hs); - - return out; -} - -#if defined(STBI_SSE2) || defined(STBI_NEON) -static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // need to generate 2x2 samples for every one in input - int i=0,t0,t1; - - if (w == 1) { - out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); - return out; - } - - t1 = 3*in_near[0] + in_far[0]; - // process groups of 8 pixels for as long as we can. - // note we can't handle the last pixel in a row in this loop - // because we need to handle the filter boundary conditions. - for (; i < ((w-1) & ~7); i += 8) { -#if defined(STBI_SSE2) - // load and perform the vertical filtering pass - // this uses 3*x + y = 4*x + (y - x) - __m128i zero = _mm_setzero_si128(); - __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); - __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); - __m128i farw = _mm_unpacklo_epi8(farb, zero); - __m128i nearw = _mm_unpacklo_epi8(nearb, zero); - __m128i diff = _mm_sub_epi16(farw, nearw); - __m128i nears = _mm_slli_epi16(nearw, 2); - __m128i curr = _mm_add_epi16(nears, diff); // current row - - // horizontal filter works the same based on shifted vers of current - // row. "prev" is current row shifted right by 1 pixel; we need to - // insert the previous pixel value (from t1). - // "next" is current row shifted left by 1 pixel, with first pixel - // of next block of 8 pixels added in. - __m128i prv0 = _mm_slli_si128(curr, 2); - __m128i nxt0 = _mm_srli_si128(curr, 2); - __m128i prev = _mm_insert_epi16(prv0, t1, 0); - __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); - - // horizontal filter, polyphase implementation since it's convenient: - // even pixels = 3*cur + prev = cur*4 + (prev - cur) - // odd pixels = 3*cur + next = cur*4 + (next - cur) - // note the shared term. - __m128i bias = _mm_set1_epi16(8); - __m128i curs = _mm_slli_epi16(curr, 2); - __m128i prvd = _mm_sub_epi16(prev, curr); - __m128i nxtd = _mm_sub_epi16(next, curr); - __m128i curb = _mm_add_epi16(curs, bias); - __m128i even = _mm_add_epi16(prvd, curb); - __m128i odd = _mm_add_epi16(nxtd, curb); - - // interleave even and odd pixels, then undo scaling. - __m128i int0 = _mm_unpacklo_epi16(even, odd); - __m128i int1 = _mm_unpackhi_epi16(even, odd); - __m128i de0 = _mm_srli_epi16(int0, 4); - __m128i de1 = _mm_srli_epi16(int1, 4); - - // pack and write output - __m128i outv = _mm_packus_epi16(de0, de1); - _mm_storeu_si128((__m128i *) (out + i*2), outv); -#elif defined(STBI_NEON) - // load and perform the vertical filtering pass - // this uses 3*x + y = 4*x + (y - x) - uint8x8_t farb = vld1_u8(in_far + i); - uint8x8_t nearb = vld1_u8(in_near + i); - int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); - int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); - int16x8_t curr = vaddq_s16(nears, diff); // current row - - // horizontal filter works the same based on shifted vers of current - // row. "prev" is current row shifted right by 1 pixel; we need to - // insert the previous pixel value (from t1). - // "next" is current row shifted left by 1 pixel, with first pixel - // of next block of 8 pixels added in. - int16x8_t prv0 = vextq_s16(curr, curr, 7); - int16x8_t nxt0 = vextq_s16(curr, curr, 1); - int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); - int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); - - // horizontal filter, polyphase implementation since it's convenient: - // even pixels = 3*cur + prev = cur*4 + (prev - cur) - // odd pixels = 3*cur + next = cur*4 + (next - cur) - // note the shared term. - int16x8_t curs = vshlq_n_s16(curr, 2); - int16x8_t prvd = vsubq_s16(prev, curr); - int16x8_t nxtd = vsubq_s16(next, curr); - int16x8_t even = vaddq_s16(curs, prvd); - int16x8_t odd = vaddq_s16(curs, nxtd); - - // undo scaling and round, then store with even/odd phases interleaved - uint8x8x2_t o; - o.val[0] = vqrshrun_n_s16(even, 4); - o.val[1] = vqrshrun_n_s16(odd, 4); - vst2_u8(out + i*2, o); -#endif - - // "previous" value for next iter - t1 = 3*in_near[i+7] + in_far[i+7]; - } - - t0 = t1; - t1 = 3*in_near[i] + in_far[i]; - out[i*2] = stbi__div16(3*t1 + t0 + 8); - - for (++i; i < w; ++i) { - t0 = t1; - t1 = 3*in_near[i]+in_far[i]; - out[i*2-1] = stbi__div16(3*t0 + t1 + 8); - out[i*2 ] = stbi__div16(3*t1 + t0 + 8); - } - out[w*2-1] = stbi__div4(t1+2); - - STBI_NOTUSED(hs); - - return out; -} -#endif - -static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // resample with nearest-neighbor - int i,j; - STBI_NOTUSED(in_far); - for (i=0; i < w; ++i) - for (j=0; j < hs; ++j) - out[i*hs+j] = in_near[i]; - return out; -} - -// this is a reduced-precision calculation of YCbCr-to-RGB introduced -// to make sure the code produces the same results in both SIMD and scalar -#define stbi__float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) -static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) -{ - int i; - for (i=0; i < count; ++i) { - int y_fixed = (y[i] << 20) + (1<<19); // rounding - int r,g,b; - int cr = pcr[i] - 128; - int cb = pcb[i] - 128; - r = y_fixed + cr* stbi__float2fixed(1.40200f); - g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); - b = y_fixed + cb* stbi__float2fixed(1.77200f); - r >>= 20; - g >>= 20; - b >>= 20; - if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } - if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } - if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } - out[0] = (stbi_uc)r; - out[1] = (stbi_uc)g; - out[2] = (stbi_uc)b; - out[3] = 255; - out += step; - } -} - -#if defined(STBI_SSE2) || defined(STBI_NEON) -static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) -{ - int i = 0; - -#ifdef STBI_SSE2 - // step == 3 is pretty ugly on the final interleave, and i'm not convinced - // it's useful in practice (you wouldn't use it for textures, for example). - // so just accelerate step == 4 case. - if (step == 4) { - // this is a fairly straightforward implementation and not super-optimized. - __m128i signflip = _mm_set1_epi8(-0x80); - __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); - __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); - __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); - __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); - __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); - __m128i xw = _mm_set1_epi16(255); // alpha channel - - for (; i+7 < count; i += 8) { - // load - __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); - __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); - __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); - __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 - __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 - - // unpack to short (and left-shift cr, cb by 8) - __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); - __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); - __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); - - // color transform - __m128i yws = _mm_srli_epi16(yw, 4); - __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); - __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); - __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); - __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); - __m128i rws = _mm_add_epi16(cr0, yws); - __m128i gwt = _mm_add_epi16(cb0, yws); - __m128i bws = _mm_add_epi16(yws, cb1); - __m128i gws = _mm_add_epi16(gwt, cr1); - - // descale - __m128i rw = _mm_srai_epi16(rws, 4); - __m128i bw = _mm_srai_epi16(bws, 4); - __m128i gw = _mm_srai_epi16(gws, 4); - - // back to byte, set up for transpose - __m128i brb = _mm_packus_epi16(rw, bw); - __m128i gxb = _mm_packus_epi16(gw, xw); - - // transpose to interleave channels - __m128i t0 = _mm_unpacklo_epi8(brb, gxb); - __m128i t1 = _mm_unpackhi_epi8(brb, gxb); - __m128i o0 = _mm_unpacklo_epi16(t0, t1); - __m128i o1 = _mm_unpackhi_epi16(t0, t1); - - // store - _mm_storeu_si128((__m128i *) (out + 0), o0); - _mm_storeu_si128((__m128i *) (out + 16), o1); - out += 32; - } - } -#endif - -#ifdef STBI_NEON - // in this version, step=3 support would be easy to add. but is there demand? - if (step == 4) { - // this is a fairly straightforward implementation and not super-optimized. - uint8x8_t signflip = vdup_n_u8(0x80); - int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); - int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); - int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); - int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); - - for (; i+7 < count; i += 8) { - // load - uint8x8_t y_bytes = vld1_u8(y + i); - uint8x8_t cr_bytes = vld1_u8(pcr + i); - uint8x8_t cb_bytes = vld1_u8(pcb + i); - int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); - int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); - - // expand to s16 - int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); - int16x8_t crw = vshll_n_s8(cr_biased, 7); - int16x8_t cbw = vshll_n_s8(cb_biased, 7); - - // color transform - int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); - int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); - int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); - int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); - int16x8_t rws = vaddq_s16(yws, cr0); - int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); - int16x8_t bws = vaddq_s16(yws, cb1); - - // undo scaling, round, convert to byte - uint8x8x4_t o; - o.val[0] = vqrshrun_n_s16(rws, 4); - o.val[1] = vqrshrun_n_s16(gws, 4); - o.val[2] = vqrshrun_n_s16(bws, 4); - o.val[3] = vdup_n_u8(255); - - // store, interleaving r/g/b/a - vst4_u8(out, o); - out += 8*4; - } - } -#endif - - for (; i < count; ++i) { - int y_fixed = (y[i] << 20) + (1<<19); // rounding - int r,g,b; - int cr = pcr[i] - 128; - int cb = pcb[i] - 128; - r = y_fixed + cr* stbi__float2fixed(1.40200f); - g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); - b = y_fixed + cb* stbi__float2fixed(1.77200f); - r >>= 20; - g >>= 20; - b >>= 20; - if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } - if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } - if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } - out[0] = (stbi_uc)r; - out[1] = (stbi_uc)g; - out[2] = (stbi_uc)b; - out[3] = 255; - out += step; - } -} -#endif - -// set up the kernels -static void stbi__setup_jpeg(stbi__jpeg *j) -{ - j->idct_block_kernel = stbi__idct_block; - j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; - j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; - -#ifdef STBI_SSE2 - if (stbi__sse2_available()) { - j->idct_block_kernel = stbi__idct_simd; - j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; - j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; - } -#endif - -#ifdef STBI_NEON - j->idct_block_kernel = stbi__idct_simd; - j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; - j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; -#endif -} - -// clean up the temporary component buffers -static void stbi__cleanup_jpeg(stbi__jpeg *j) -{ - stbi__free_jpeg_components(j, j->s->img_n, 0); -} - -typedef struct -{ - resample_row_func resample; - stbi_uc *line0,*line1; - int hs,vs; // expansion factor in each axis - int w_lores; // horizontal pixels pre-expansion - int ystep; // how far through vertical expansion we are - int ypos; // which pre-expansion row we're on -} stbi__resample; - -// fast 0..255 * 0..255 => 0..255 rounded multiplication -static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y) -{ - unsigned int t = x*y + 128; - return (stbi_uc) ((t + (t >>8)) >> 8); -} - -static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) -{ - int n, decode_n, is_rgb; - z->s->img_n = 0; // make stbi__cleanup_jpeg safe - - // validate req_comp - if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); - - // load a jpeg image from whichever source, but leave in YCbCr format - if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } - - // determine actual number of components to generate - n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1; - - is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif)); - - if (z->s->img_n == 3 && n < 3 && !is_rgb) - decode_n = 1; - else - decode_n = z->s->img_n; - - // resample and color-convert - { - int k; - unsigned int i,j; - stbi_uc *output; - stbi_uc *coutput[4]; - - stbi__resample res_comp[4]; - - for (k=0; k < decode_n; ++k) { - stbi__resample *r = &res_comp[k]; - - // allocate line buffer big enough for upsampling off the edges - // with upsample factor of 4 - z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); - if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } - - r->hs = z->img_h_max / z->img_comp[k].h; - r->vs = z->img_v_max / z->img_comp[k].v; - r->ystep = r->vs >> 1; - r->w_lores = (z->s->img_x + r->hs-1) / r->hs; - r->ypos = 0; - r->line0 = r->line1 = z->img_comp[k].data; - - if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; - else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; - else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; - else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; - else r->resample = stbi__resample_row_generic; - } - - // can't error after this so, this is safe - output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1); - if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } - - // now go ahead and resample - for (j=0; j < z->s->img_y; ++j) { - stbi_uc *out = output + n * z->s->img_x * j; - for (k=0; k < decode_n; ++k) { - stbi__resample *r = &res_comp[k]; - int y_bot = r->ystep >= (r->vs >> 1); - coutput[k] = r->resample(z->img_comp[k].linebuf, - y_bot ? r->line1 : r->line0, - y_bot ? r->line0 : r->line1, - r->w_lores, r->hs); - if (++r->ystep >= r->vs) { - r->ystep = 0; - r->line0 = r->line1; - if (++r->ypos < z->img_comp[k].y) - r->line1 += z->img_comp[k].w2; - } - } - if (n >= 3) { - stbi_uc *y = coutput[0]; - if (z->s->img_n == 3) { - if (is_rgb) { - for (i=0; i < z->s->img_x; ++i) { - out[0] = y[i]; - out[1] = coutput[1][i]; - out[2] = coutput[2][i]; - out[3] = 255; - out += n; - } - } else { - z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); - } - } else if (z->s->img_n == 4) { - if (z->app14_color_transform == 0) { // CMYK - for (i=0; i < z->s->img_x; ++i) { - stbi_uc k = coutput[3][i]; - out[0] = stbi__blinn_8x8(coutput[0][i], k); - out[1] = stbi__blinn_8x8(coutput[1][i], k); - out[2] = stbi__blinn_8x8(coutput[2][i], k); - out[3] = 255; - out += n; - } - } else if (z->app14_color_transform == 2) { // YCCK - z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); - for (i=0; i < z->s->img_x; ++i) { - stbi_uc k = coutput[3][i]; - out[0] = stbi__blinn_8x8(255 - out[0], k); - out[1] = stbi__blinn_8x8(255 - out[1], k); - out[2] = stbi__blinn_8x8(255 - out[2], k); - out += n; - } - } else { // YCbCr + alpha? Ignore the fourth channel for now - z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); - } - } else - for (i=0; i < z->s->img_x; ++i) { - out[0] = out[1] = out[2] = y[i]; - out[3] = 255; // not used if n==3 - out += n; - } - } else { - if (is_rgb) { - if (n == 1) - for (i=0; i < z->s->img_x; ++i) - *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); - else { - for (i=0; i < z->s->img_x; ++i, out += 2) { - out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); - out[1] = 255; - } - } - } else if (z->s->img_n == 4 && z->app14_color_transform == 0) { - for (i=0; i < z->s->img_x; ++i) { - stbi_uc k = coutput[3][i]; - stbi_uc r = stbi__blinn_8x8(coutput[0][i], k); - stbi_uc g = stbi__blinn_8x8(coutput[1][i], k); - stbi_uc b = stbi__blinn_8x8(coutput[2][i], k); - out[0] = stbi__compute_y(r, g, b); - out[1] = 255; - out += n; - } - } else if (z->s->img_n == 4 && z->app14_color_transform == 2) { - for (i=0; i < z->s->img_x; ++i) { - out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]); - out[1] = 255; - out += n; - } - } else { - stbi_uc *y = coutput[0]; - if (n == 1) - for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; - else - for (i=0; i < z->s->img_x; ++i) *out++ = y[i], *out++ = 255; - } - } - } - stbi__cleanup_jpeg(z); - *out_x = z->s->img_x; - *out_y = z->s->img_y; - if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output - return output; - } -} - -static void * STBI_FORCE_STACK_ALIGN stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - unsigned char* result; - stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); - STBI_NOTUSED(ri); - j->s = s; - stbi__setup_jpeg(j); - result = load_jpeg_image(j, x,y,comp,req_comp); - STBI_FREE(j); - return result; -} - -static int stbi__jpeg_test(stbi__context *s) -{ - int r; - stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg)); - j->s = s; - stbi__setup_jpeg(j); - r = stbi__decode_jpeg_header(j, STBI__SCAN_type); - stbi__rewind(s); - STBI_FREE(j); - return r; -} - -static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) -{ - if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { - stbi__rewind( j->s ); - return 0; - } - if (x) *x = j->s->img_x; - if (y) *y = j->s->img_y; - if (comp) *comp = j->s->img_n >= 3 ? 3 : 1; - return 1; -} - -static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) -{ - int result; - stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); - j->s = s; - result = stbi__jpeg_info_raw(j, x, y, comp); - STBI_FREE(j); - return result; -} -#endif - -// public domain zlib decode v0.2 Sean Barrett 2006-11-18 -// simple implementation -// - all input must be provided in an upfront buffer -// - all output is written to a single output buffer (can malloc/realloc) -// performance -// - fast huffman - -#ifndef STBI_NO_ZLIB - -// fast-way is faster to check than jpeg huffman, but slow way is slower -#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables -#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) - -// zlib-style huffman encoding -// (jpegs packs from left, zlib from right, so can't share code) -typedef struct -{ - stbi__uint16 fast[1 << STBI__ZFAST_BITS]; - stbi__uint16 firstcode[16]; - int maxcode[17]; - stbi__uint16 firstsymbol[16]; - stbi_uc size[288]; - stbi__uint16 value[288]; -} stbi__zhuffman; - -stbi_inline static int stbi__bitreverse16(int n) -{ - n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); - n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); - n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); - n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); - return n; -} - -stbi_inline static int stbi__bit_reverse(int v, int bits) -{ - STBI_ASSERT(bits <= 16); - // to bit reverse n bits, reverse 16 and shift - // e.g. 11 bits, bit reverse and shift away 5 - return stbi__bitreverse16(v) >> (16-bits); -} - -static int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num) -{ - int i,k=0; - int code, next_code[16], sizes[17]; - - // DEFLATE spec for generating codes - memset(sizes, 0, sizeof(sizes)); - memset(z->fast, 0, sizeof(z->fast)); - for (i=0; i < num; ++i) - ++sizes[sizelist[i]]; - sizes[0] = 0; - for (i=1; i < 16; ++i) - if (sizes[i] > (1 << i)) - return stbi__err("bad sizes", "Corrupt PNG"); - code = 0; - for (i=1; i < 16; ++i) { - next_code[i] = code; - z->firstcode[i] = (stbi__uint16) code; - z->firstsymbol[i] = (stbi__uint16) k; - code = (code + sizes[i]); - if (sizes[i]) - if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); - z->maxcode[i] = code << (16-i); // preshift for inner loop - code <<= 1; - k += sizes[i]; - } - z->maxcode[16] = 0x10000; // sentinel - for (i=0; i < num; ++i) { - int s = sizelist[i]; - if (s) { - int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; - stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); - z->size [c] = (stbi_uc ) s; - z->value[c] = (stbi__uint16) i; - if (s <= STBI__ZFAST_BITS) { - int j = stbi__bit_reverse(next_code[s],s); - while (j < (1 << STBI__ZFAST_BITS)) { - z->fast[j] = fastv; - j += (1 << s); - } - } - ++next_code[s]; - } - } - return 1; -} - -// zlib-from-memory implementation for PNG reading -// because PNG allows splitting the zlib stream arbitrarily, -// and it's annoying structurally to have PNG call ZLIB call PNG, -// we require PNG read all the IDATs and combine them into a single -// memory buffer - -typedef struct -{ - stbi_uc *zbuffer, *zbuffer_end; - int num_bits; - stbi__uint32 code_buffer; - - char *zout; - char *zout_start; - char *zout_end; - int z_expandable; - - stbi__zhuffman z_length, z_distance; -} stbi__zbuf; - -stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) -{ - if (z->zbuffer >= z->zbuffer_end) return 0; - return *z->zbuffer++; -} - -static void stbi__fill_bits(stbi__zbuf *z) -{ - do { - STBI_ASSERT(z->code_buffer < (1U << z->num_bits)); - z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; - z->num_bits += 8; - } while (z->num_bits <= 24); -} - -stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) -{ - unsigned int k; - if (z->num_bits < n) stbi__fill_bits(z); - k = z->code_buffer & ((1 << n) - 1); - z->code_buffer >>= n; - z->num_bits -= n; - return k; -} - -static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) -{ - int b,s,k; - // not resolved by fast table, so compute it the slow way - // use jpeg approach, which requires MSbits at top - k = stbi__bit_reverse(a->code_buffer, 16); - for (s=STBI__ZFAST_BITS+1; ; ++s) - if (k < z->maxcode[s]) - break; - if (s == 16) return -1; // invalid code! - // code size is s, so: - b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; - STBI_ASSERT(z->size[b] == s); - a->code_buffer >>= s; - a->num_bits -= s; - return z->value[b]; -} - -stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) -{ - int b,s; - if (a->num_bits < 16) stbi__fill_bits(a); - b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; - if (b) { - s = b >> 9; - a->code_buffer >>= s; - a->num_bits -= s; - return b & 511; - } - return stbi__zhuffman_decode_slowpath(a, z); -} - -static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes -{ - char *q; - int cur, limit, old_limit; - z->zout = zout; - if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); - cur = (int) (z->zout - z->zout_start); - limit = old_limit = (int) (z->zout_end - z->zout_start); - while (cur + n > limit) - limit *= 2; - q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); - STBI_NOTUSED(old_limit); - if (q == NULL) return stbi__err("outofmem", "Out of memory"); - z->zout_start = q; - z->zout = q + cur; - z->zout_end = q + limit; - return 1; -} - -static int stbi__zlength_base[31] = { - 3,4,5,6,7,8,9,10,11,13, - 15,17,19,23,27,31,35,43,51,59, - 67,83,99,115,131,163,195,227,258,0,0 }; - -static int stbi__zlength_extra[31]= -{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; - -static int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, -257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; - -static int stbi__zdist_extra[32] = -{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; - -static int stbi__parse_huffman_block(stbi__zbuf *a) -{ - char *zout = a->zout; - for(;;) { - int z = stbi__zhuffman_decode(a, &a->z_length); - if (z < 256) { - if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes - if (zout >= a->zout_end) { - if (!stbi__zexpand(a, zout, 1)) return 0; - zout = a->zout; - } - *zout++ = (char) z; - } else { - stbi_uc *p; - int len,dist; - if (z == 256) { - a->zout = zout; - return 1; - } - z -= 257; - len = stbi__zlength_base[z]; - if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); - z = stbi__zhuffman_decode(a, &a->z_distance); - if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); - dist = stbi__zdist_base[z]; - if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); - if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); - if (zout + len > a->zout_end) { - if (!stbi__zexpand(a, zout, len)) return 0; - zout = a->zout; - } - p = (stbi_uc *) (zout - dist); - if (dist == 1) { // run of one byte; common in images. - stbi_uc v = *p; - if (len) { do *zout++ = v; while (--len); } - } else { - if (len) { do *zout++ = *p++; while (--len); } - } - } - } -} - -static int stbi__compute_huffman_codes(stbi__zbuf *a) -{ - static stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; - stbi__zhuffman z_codelength; - stbi_uc lencodes[286+32+137];//padding for maximum single op - stbi_uc codelength_sizes[19]; - int i,n; - - int hlit = stbi__zreceive(a,5) + 257; - int hdist = stbi__zreceive(a,5) + 1; - int hclen = stbi__zreceive(a,4) + 4; - int ntot = hlit + hdist; - - memset(codelength_sizes, 0, sizeof(codelength_sizes)); - for (i=0; i < hclen; ++i) { - int s = stbi__zreceive(a,3); - codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; - } - if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; - - n = 0; - while (n < ntot) { - int c = stbi__zhuffman_decode(a, &z_codelength); - if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); - if (c < 16) - lencodes[n++] = (stbi_uc) c; - else { - stbi_uc fill = 0; - if (c == 16) { - c = stbi__zreceive(a,2)+3; - if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG"); - fill = lencodes[n-1]; - } else if (c == 17) - c = stbi__zreceive(a,3)+3; - else { - STBI_ASSERT(c == 18); - c = stbi__zreceive(a,7)+11; - } - if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG"); - memset(lencodes+n, fill, c); - n += c; - } - } - if (n != ntot) return stbi__err("bad codelengths","Corrupt PNG"); - if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; - if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; - return 1; -} - -static int stbi__parse_uncompressed_block(stbi__zbuf *a) -{ - stbi_uc header[4]; - int len,nlen,k; - if (a->num_bits & 7) - stbi__zreceive(a, a->num_bits & 7); // discard - // drain the bit-packed data into header - k = 0; - while (a->num_bits > 0) { - header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check - a->code_buffer >>= 8; - a->num_bits -= 8; - } - STBI_ASSERT(a->num_bits == 0); - // now fill header the normal way - while (k < 4) - header[k++] = stbi__zget8(a); - len = header[1] * 256 + header[0]; - nlen = header[3] * 256 + header[2]; - if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); - if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); - if (a->zout + len > a->zout_end) - if (!stbi__zexpand(a, a->zout, len)) return 0; - memcpy(a->zout, a->zbuffer, len); - a->zbuffer += len; - a->zout += len; - return 1; -} - -static int stbi__parse_zlib_header(stbi__zbuf *a) -{ - int cmf = stbi__zget8(a); - int cm = cmf & 15; - /* int cinfo = cmf >> 4; */ - int flg = stbi__zget8(a); - if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec - if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png - if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png - // window = 1 << (8 + cinfo)... but who cares, we fully buffer output - return 1; -} - -static const stbi_uc stbi__zdefault_length[288] = -{ - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8 -}; -static const stbi_uc stbi__zdefault_distance[32] = -{ - 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5 -}; -/* -Init algorithm: -{ - int i; // use <= to match clearly with spec - for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; - for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; - for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; - for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; - - for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; -} -*/ - -static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) -{ - int final, type; - if (parse_header) - if (!stbi__parse_zlib_header(a)) return 0; - a->num_bits = 0; - a->code_buffer = 0; - do { - final = stbi__zreceive(a,1); - type = stbi__zreceive(a,2); - if (type == 0) { - if (!stbi__parse_uncompressed_block(a)) return 0; - } else if (type == 3) { - return 0; - } else { - if (type == 1) { - // use fixed code lengths - if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , 288)) return 0; - if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; - } else { - if (!stbi__compute_huffman_codes(a)) return 0; - } - if (!stbi__parse_huffman_block(a)) return 0; - } - } while (!final); - return 1; -} - -static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) -{ - a->zout_start = obuf; - a->zout = obuf; - a->zout_end = obuf + olen; - a->z_expandable = exp; - - return stbi__parse_zlib(a, parse_header); -} - -STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) -{ - stbi__zbuf a; - char *p = (char *) stbi__malloc(initial_size); - if (p == NULL) return NULL; - a.zbuffer = (stbi_uc *) buffer; - a.zbuffer_end = (stbi_uc *) buffer + len; - if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { - if (outlen) *outlen = (int) (a.zout - a.zout_start); - return a.zout_start; - } else { - STBI_FREE(a.zout_start); - return NULL; - } -} - -STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) -{ - return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); -} - -STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) -{ - stbi__zbuf a; - char *p = (char *) stbi__malloc(initial_size); - if (p == NULL) return NULL; - a.zbuffer = (stbi_uc *) buffer; - a.zbuffer_end = (stbi_uc *) buffer + len; - if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { - if (outlen) *outlen = (int) (a.zout - a.zout_start); - return a.zout_start; - } else { - STBI_FREE(a.zout_start); - return NULL; - } -} - -STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) -{ - stbi__zbuf a; - a.zbuffer = (stbi_uc *) ibuffer; - a.zbuffer_end = (stbi_uc *) ibuffer + ilen; - if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) - return (int) (a.zout - a.zout_start); - else - return -1; -} - -STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) -{ - stbi__zbuf a; - char *p = (char *) stbi__malloc(16384); - if (p == NULL) return NULL; - a.zbuffer = (stbi_uc *) buffer; - a.zbuffer_end = (stbi_uc *) buffer+len; - if (stbi__do_zlib(&a, p, 16384, 1, 0)) { - if (outlen) *outlen = (int) (a.zout - a.zout_start); - return a.zout_start; - } else { - STBI_FREE(a.zout_start); - return NULL; - } -} - -STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) -{ - stbi__zbuf a; - a.zbuffer = (stbi_uc *) ibuffer; - a.zbuffer_end = (stbi_uc *) ibuffer + ilen; - if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) - return (int) (a.zout - a.zout_start); - else - return -1; -} -#endif - -// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 -// simple implementation -// - only 8-bit samples -// - no CRC checking -// - allocates lots of intermediate memory -// - avoids problem of streaming data between subsystems -// - avoids explicit window management -// performance -// - uses stb_zlib, a PD zlib implementation with fast huffman decoding - -#ifndef STBI_NO_PNG -typedef struct -{ - stbi__uint32 length; - stbi__uint32 type; -} stbi__pngchunk; - -static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) -{ - stbi__pngchunk c; - c.length = stbi__get32be(s); - c.type = stbi__get32be(s); - return c; -} - -static int stbi__check_png_header(stbi__context *s) -{ - static stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; - int i; - for (i=0; i < 8; ++i) - if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); - return 1; -} - -typedef struct -{ - stbi__context *s; - stbi_uc *idata, *expanded, *out; - int depth; -} stbi__png; - - -enum { - STBI__F_none=0, - STBI__F_sub=1, - STBI__F_up=2, - STBI__F_avg=3, - STBI__F_paeth=4, - // synthetic filters used for first scanline to avoid needing a dummy row of 0s - STBI__F_avg_first, - STBI__F_paeth_first -}; - -static stbi_uc first_row_filter[5] = -{ - STBI__F_none, - STBI__F_sub, - STBI__F_none, - STBI__F_avg_first, - STBI__F_paeth_first -}; - -static int stbi__paeth(int a, int b, int c) -{ - int p = a + b - c; - int pa = abs(p-a); - int pb = abs(p-b); - int pc = abs(p-c); - if (pa <= pb && pa <= pc) return a; - if (pb <= pc) return b; - return c; -} - -static stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; - -// create the png data from post-deflated data -static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) -{ - int bytes = (depth == 16? 2 : 1); - stbi__context *s = a->s; - stbi__uint32 i,j,stride = x*out_n*bytes; - stbi__uint32 img_len, img_width_bytes; - int k; - int img_n = s->img_n; // copy it into a local for later - - int output_bytes = out_n*bytes; - int filter_bytes = img_n*bytes; - int width = x; - - STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); - a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into - if (!a->out) return stbi__err("outofmem", "Out of memory"); - - img_width_bytes = (((img_n * x * depth) + 7) >> 3); - img_len = (img_width_bytes + 1) * y; - if (s->img_x == x && s->img_y == y) { - if (raw_len != img_len) return stbi__err("not enough pixels","Corrupt PNG"); - } else { // interlaced: - if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); - } - - for (j=0; j < y; ++j) { - stbi_uc *cur = a->out + stride*j; - stbi_uc *prior; - int filter = *raw++; - - if (filter > 4) - return stbi__err("invalid filter","Corrupt PNG"); - - if (depth < 8) { - STBI_ASSERT(img_width_bytes <= x); - cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place - filter_bytes = 1; - width = img_width_bytes; - } - prior = cur - stride; // bugfix: need to compute this after 'cur +=' computation above - - // if first row, use special filter that doesn't sample previous row - if (j == 0) filter = first_row_filter[filter]; - - // handle first byte explicitly - for (k=0; k < filter_bytes; ++k) { - switch (filter) { - case STBI__F_none : cur[k] = raw[k]; break; - case STBI__F_sub : cur[k] = raw[k]; break; - case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; - case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break; - case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break; - case STBI__F_avg_first : cur[k] = raw[k]; break; - case STBI__F_paeth_first: cur[k] = raw[k]; break; - } - } - - if (depth == 8) { - if (img_n != out_n) - cur[img_n] = 255; // first pixel - raw += img_n; - cur += out_n; - prior += out_n; - } else if (depth == 16) { - if (img_n != out_n) { - cur[filter_bytes] = 255; // first pixel top byte - cur[filter_bytes+1] = 255; // first pixel bottom byte - } - raw += filter_bytes; - cur += output_bytes; - prior += output_bytes; - } else { - raw += 1; - cur += 1; - prior += 1; - } - - // this is a little gross, so that we don't switch per-pixel or per-component - if (depth < 8 || img_n == out_n) { - int nk = (width - 1)*filter_bytes; - #define STBI__CASE(f) \ - case f: \ - for (k=0; k < nk; ++k) - switch (filter) { - // "none" filter turns into a memcpy here; make that explicit. - case STBI__F_none: memcpy(cur, raw, nk); break; - STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break; - STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; - STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break; - STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break; - STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break; - STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break; - } - #undef STBI__CASE - raw += nk; - } else { - STBI_ASSERT(img_n+1 == out_n); - #define STBI__CASE(f) \ - case f: \ - for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \ - for (k=0; k < filter_bytes; ++k) - switch (filter) { - STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break; - STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break; - STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; - STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break; - STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break; - STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break; - STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break; - } - #undef STBI__CASE - - // the loop above sets the high byte of the pixels' alpha, but for - // 16 bit png files we also need the low byte set. we'll do that here. - if (depth == 16) { - cur = a->out + stride*j; // start at the beginning of the row again - for (i=0; i < x; ++i,cur+=output_bytes) { - cur[filter_bytes+1] = 255; - } - } - } - } - - // we make a separate pass to expand bits to pixels; for performance, - // this could run two scanlines behind the above code, so it won't - // intefere with filtering but will still be in the cache. - if (depth < 8) { - for (j=0; j < y; ++j) { - stbi_uc *cur = a->out + stride*j; - stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes; - // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit - // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop - stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range - - // note that the final byte might overshoot and write more data than desired. - // we can allocate enough data that this never writes out of memory, but it - // could also overwrite the next scanline. can it overwrite non-empty data - // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel. - // so we need to explicitly clamp the final ones - - if (depth == 4) { - for (k=x*img_n; k >= 2; k-=2, ++in) { - *cur++ = scale * ((*in >> 4) ); - *cur++ = scale * ((*in ) & 0x0f); - } - if (k > 0) *cur++ = scale * ((*in >> 4) ); - } else if (depth == 2) { - for (k=x*img_n; k >= 4; k-=4, ++in) { - *cur++ = scale * ((*in >> 6) ); - *cur++ = scale * ((*in >> 4) & 0x03); - *cur++ = scale * ((*in >> 2) & 0x03); - *cur++ = scale * ((*in ) & 0x03); - } - if (k > 0) *cur++ = scale * ((*in >> 6) ); - if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); - if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); - } else if (depth == 1) { - for (k=x*img_n; k >= 8; k-=8, ++in) { - *cur++ = scale * ((*in >> 7) ); - *cur++ = scale * ((*in >> 6) & 0x01); - *cur++ = scale * ((*in >> 5) & 0x01); - *cur++ = scale * ((*in >> 4) & 0x01); - *cur++ = scale * ((*in >> 3) & 0x01); - *cur++ = scale * ((*in >> 2) & 0x01); - *cur++ = scale * ((*in >> 1) & 0x01); - *cur++ = scale * ((*in ) & 0x01); - } - if (k > 0) *cur++ = scale * ((*in >> 7) ); - if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); - if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); - if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); - if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); - if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); - if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); - } - if (img_n != out_n) { - int q; - // insert alpha = 255 - cur = a->out + stride*j; - if (img_n == 1) { - for (q=x-1; q >= 0; --q) { - cur[q*2+1] = 255; - cur[q*2+0] = cur[q]; - } - } else { - STBI_ASSERT(img_n == 3); - for (q=x-1; q >= 0; --q) { - cur[q*4+3] = 255; - cur[q*4+2] = cur[q*3+2]; - cur[q*4+1] = cur[q*3+1]; - cur[q*4+0] = cur[q*3+0]; - } - } - } - } - } else if (depth == 16) { - // force the image data from big-endian to platform-native. - // this is done in a separate pass due to the decoding relying - // on the data being untouched, but could probably be done - // per-line during decode if care is taken. - stbi_uc *cur = a->out; - stbi__uint16 *cur16 = (stbi__uint16*)cur; - - for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) { - *cur16 = (cur[0] << 8) | cur[1]; - } - } - - return 1; -} - -static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) -{ - int bytes = (depth == 16 ? 2 : 1); - int out_bytes = out_n * bytes; - stbi_uc *final; - int p; - if (!interlaced) - return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); - - // de-interlacing - final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); - for (p=0; p < 7; ++p) { - int xorig[] = { 0,4,0,2,0,1,0 }; - int yorig[] = { 0,0,4,0,2,0,1 }; - int xspc[] = { 8,8,4,4,2,2,1 }; - int yspc[] = { 8,8,8,4,4,2,2 }; - int i,j,x,y; - // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 - x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; - y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; - if (x && y) { - stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; - if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { - STBI_FREE(final); - return 0; - } - for (j=0; j < y; ++j) { - for (i=0; i < x; ++i) { - int out_y = j*yspc[p]+yorig[p]; - int out_x = i*xspc[p]+xorig[p]; - memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes, - a->out + (j*x+i)*out_bytes, out_bytes); - } - } - STBI_FREE(a->out); - image_data += img_len; - image_data_len -= img_len; - } - } - a->out = final; - - return 1; -} - -static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) -{ - stbi__context *s = z->s; - stbi__uint32 i, pixel_count = s->img_x * s->img_y; - stbi_uc *p = z->out; - - // compute color-based transparency, assuming we've - // already got 255 as the alpha value in the output - STBI_ASSERT(out_n == 2 || out_n == 4); - - if (out_n == 2) { - for (i=0; i < pixel_count; ++i) { - p[1] = (p[0] == tc[0] ? 0 : 255); - p += 2; - } - } else { - for (i=0; i < pixel_count; ++i) { - if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) - p[3] = 0; - p += 4; - } - } - return 1; -} - -static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n) -{ - stbi__context *s = z->s; - stbi__uint32 i, pixel_count = s->img_x * s->img_y; - stbi__uint16 *p = (stbi__uint16*) z->out; - - // compute color-based transparency, assuming we've - // already got 65535 as the alpha value in the output - STBI_ASSERT(out_n == 2 || out_n == 4); - - if (out_n == 2) { - for (i = 0; i < pixel_count; ++i) { - p[1] = (p[0] == tc[0] ? 0 : 65535); - p += 2; - } - } else { - for (i = 0; i < pixel_count; ++i) { - if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) - p[3] = 0; - p += 4; - } - } - return 1; -} - -static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) -{ - stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; - stbi_uc *p, *temp_out, *orig = a->out; - - p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0); - if (p == NULL) return stbi__err("outofmem", "Out of memory"); - - // between here and free(out) below, exitting would leak - temp_out = p; - - if (pal_img_n == 3) { - for (i=0; i < pixel_count; ++i) { - int n = orig[i]*4; - p[0] = palette[n ]; - p[1] = palette[n+1]; - p[2] = palette[n+2]; - p += 3; - } - } else { - for (i=0; i < pixel_count; ++i) { - int n = orig[i]*4; - p[0] = palette[n ]; - p[1] = palette[n+1]; - p[2] = palette[n+2]; - p[3] = palette[n+3]; - p += 4; - } - } - STBI_FREE(a->out); - a->out = temp_out; - - STBI_NOTUSED(len); - - return 1; -} - -static int stbi__unpremultiply_on_load = 0; -static int stbi__de_iphone_flag = 0; - -STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) -{ - stbi__unpremultiply_on_load = flag_true_if_should_unpremultiply; -} - -STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) -{ - stbi__de_iphone_flag = flag_true_if_should_convert; -} - -static void stbi__de_iphone(stbi__png *z) -{ - stbi__context *s = z->s; - stbi__uint32 i, pixel_count = s->img_x * s->img_y; - stbi_uc *p = z->out; - - if (s->img_out_n == 3) { // convert bgr to rgb - for (i=0; i < pixel_count; ++i) { - stbi_uc t = p[0]; - p[0] = p[2]; - p[2] = t; - p += 3; - } - } else { - STBI_ASSERT(s->img_out_n == 4); - if (stbi__unpremultiply_on_load) { - // convert bgr to rgb and unpremultiply - for (i=0; i < pixel_count; ++i) { - stbi_uc a = p[3]; - stbi_uc t = p[0]; - if (a) { - p[0] = p[2] * 255 / a; - p[1] = p[1] * 255 / a; - p[2] = t * 255 / a; - } else { - p[0] = p[2]; - p[2] = t; - } - p += 4; - } - } else { - // convert bgr to rgb - for (i=0; i < pixel_count; ++i) { - stbi_uc t = p[0]; - p[0] = p[2]; - p[2] = t; - p += 4; - } - } - } -} - -#define STBI__PNG_TYPE(a,b,c,d) (((a) << 24) + ((b) << 16) + ((c) << 8) + (d)) - -static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) -{ - stbi_uc palette[1024], pal_img_n=0; - stbi_uc has_trans=0, tc[3]; - stbi__uint16 tc16[3]; - stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; - int first=1,k,interlace=0, color=0, is_iphone=0; - stbi__context *s = z->s; - - z->expanded = NULL; - z->idata = NULL; - z->out = NULL; - - if (!stbi__check_png_header(s)) return 0; - - if (scan == STBI__SCAN_type) return 1; - - for (;;) { - stbi__pngchunk c = stbi__get_chunk_header(s); - switch (c.type) { - case STBI__PNG_TYPE('C','g','B','I'): - is_iphone = 1; - stbi__skip(s, c.length); - break; - case STBI__PNG_TYPE('I','H','D','R'): { - int comp,filter; - if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); - first = 0; - if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); - s->img_x = stbi__get32be(s); if (s->img_x > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); - s->img_y = stbi__get32be(s); if (s->img_y > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); - z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only"); - color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); - if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG"); - if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); - comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); - filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); - interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); - if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); - if (!pal_img_n) { - s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); - if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); - if (scan == STBI__SCAN_header) return 1; - } else { - // if paletted, then pal_n is our final components, and - // img_n is # components to decompress/filter. - s->img_n = 1; - if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); - // if SCAN_header, have to scan to see if we have a tRNS - } - break; - } - - case STBI__PNG_TYPE('P','L','T','E'): { - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); - pal_len = c.length / 3; - if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); - for (i=0; i < pal_len; ++i) { - palette[i*4+0] = stbi__get8(s); - palette[i*4+1] = stbi__get8(s); - palette[i*4+2] = stbi__get8(s); - palette[i*4+3] = 255; - } - break; - } - - case STBI__PNG_TYPE('t','R','N','S'): { - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); - if (pal_img_n) { - if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } - if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); - if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); - pal_img_n = 4; - for (i=0; i < c.length; ++i) - palette[i*4+3] = stbi__get8(s); - } else { - if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); - if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); - has_trans = 1; - if (z->depth == 16) { - for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is - } else { - for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger - } - } - break; - } - - case STBI__PNG_TYPE('I','D','A','T'): { - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); - if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; } - if ((int)(ioff + c.length) < (int)ioff) return 0; - if (ioff + c.length > idata_limit) { - stbi__uint32 idata_limit_old = idata_limit; - stbi_uc *p; - if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; - while (ioff + c.length > idata_limit) - idata_limit *= 2; - STBI_NOTUSED(idata_limit_old); - p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); - z->idata = p; - } - if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); - ioff += c.length; - break; - } - - case STBI__PNG_TYPE('I','E','N','D'): { - stbi__uint32 raw_len, bpl; - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if (scan != STBI__SCAN_load) return 1; - if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); - // initial guess for decoded data size to avoid unnecessary reallocs - bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component - raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; - z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); - if (z->expanded == NULL) return 0; // zlib should set error - STBI_FREE(z->idata); z->idata = NULL; - if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) - s->img_out_n = s->img_n+1; - else - s->img_out_n = s->img_n; - if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; - if (has_trans) { - if (z->depth == 16) { - if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; - } else { - if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; - } - } - if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) - stbi__de_iphone(z); - if (pal_img_n) { - // pal_img_n == 3 or 4 - s->img_n = pal_img_n; // record the actual colors we had - s->img_out_n = pal_img_n; - if (req_comp >= 3) s->img_out_n = req_comp; - if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) - return 0; - } - STBI_FREE(z->expanded); z->expanded = NULL; - return 1; - } - - default: - // if critical, fail - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if ((c.type & (1 << 29)) == 0) { - #ifndef STBI_NO_FAILURE_STRINGS - // not threadsafe - static char invalid_chunk[] = "XXXX PNG chunk not known"; - invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); - invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); - invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); - invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); - #endif - return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); - } - stbi__skip(s, c.length); - break; - } - // end of PNG chunk, read and skip CRC - stbi__get32be(s); - } -} - -static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri) -{ - void *result=NULL; - if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); - if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { - if (p->depth < 8) - ri->bits_per_channel = 8; - else - ri->bits_per_channel = p->depth; - result = p->out; - p->out = NULL; - if (req_comp && req_comp != p->s->img_out_n) { - if (ri->bits_per_channel == 8) - result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); - else - result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); - p->s->img_out_n = req_comp; - if (result == NULL) return result; - } - *x = p->s->img_x; - *y = p->s->img_y; - if (n) *n = p->s->img_n; - } - STBI_FREE(p->out); p->out = NULL; - STBI_FREE(p->expanded); p->expanded = NULL; - STBI_FREE(p->idata); p->idata = NULL; - - return result; -} - -static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - stbi__png p; - p.s = s; - return stbi__do_png(&p, x,y,comp,req_comp, ri); -} - -static int stbi__png_test(stbi__context *s) -{ - int r; - r = stbi__check_png_header(s); - stbi__rewind(s); - return r; -} - -static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) -{ - if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { - stbi__rewind( p->s ); - return 0; - } - if (x) *x = p->s->img_x; - if (y) *y = p->s->img_y; - if (comp) *comp = p->s->img_n; - return 1; -} - -static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) -{ - stbi__png p; - p.s = s; - return stbi__png_info_raw(&p, x, y, comp); -} -#endif - -// Microsoft/Windows BMP image - -#ifndef STBI_NO_BMP -static int stbi__bmp_test_raw(stbi__context *s) -{ - int r; - int sz; - if (stbi__get8(s) != 'B') return 0; - if (stbi__get8(s) != 'M') return 0; - stbi__get32le(s); // discard filesize - stbi__get16le(s); // discard reserved - stbi__get16le(s); // discard reserved - stbi__get32le(s); // discard data offset - sz = stbi__get32le(s); - r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); - return r; -} - -static int stbi__bmp_test(stbi__context *s) -{ - int r = stbi__bmp_test_raw(s); - stbi__rewind(s); - return r; -} - - -// returns 0..31 for the highest set bit -static int stbi__high_bit(unsigned int z) -{ - int n=0; - if (z == 0) return -1; - if (z >= 0x10000) n += 16, z >>= 16; - if (z >= 0x00100) n += 8, z >>= 8; - if (z >= 0x00010) n += 4, z >>= 4; - if (z >= 0x00004) n += 2, z >>= 2; - if (z >= 0x00002) n += 1, z >>= 1; - return n; -} - -static int stbi__bitcount(unsigned int a) -{ - a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 - a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 - a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits - a = (a + (a >> 8)); // max 16 per 8 bits - a = (a + (a >> 16)); // max 32 per 8 bits - return a & 0xff; -} - -static int stbi__shiftsigned(int v, int shift, int bits) -{ - int result; - int z=0; - - if (shift < 0) v <<= -shift; - else v >>= shift; - result = v; - - z = bits; - while (z < 8) { - result += v >> z; - z += bits; - } - return result; -} - -typedef struct -{ - int bpp, offset, hsz; - unsigned int mr,mg,mb,ma, all_a; -} stbi__bmp_data; - -static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) -{ - int hsz; - if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); - stbi__get32le(s); // discard filesize - stbi__get16le(s); // discard reserved - stbi__get16le(s); // discard reserved - info->offset = stbi__get32le(s); - info->hsz = hsz = stbi__get32le(s); - info->mr = info->mg = info->mb = info->ma = 0; - - if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); - if (hsz == 12) { - s->img_x = stbi__get16le(s); - s->img_y = stbi__get16le(s); - } else { - s->img_x = stbi__get32le(s); - s->img_y = stbi__get32le(s); - } - if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); - info->bpp = stbi__get16le(s); - if (info->bpp == 1) return stbi__errpuc("monochrome", "BMP type not supported: 1-bit"); - if (hsz != 12) { - int compress = stbi__get32le(s); - if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); - stbi__get32le(s); // discard sizeof - stbi__get32le(s); // discard hres - stbi__get32le(s); // discard vres - stbi__get32le(s); // discard colorsused - stbi__get32le(s); // discard max important - if (hsz == 40 || hsz == 56) { - if (hsz == 56) { - stbi__get32le(s); - stbi__get32le(s); - stbi__get32le(s); - stbi__get32le(s); - } - if (info->bpp == 16 || info->bpp == 32) { - if (compress == 0) { - if (info->bpp == 32) { - info->mr = 0xffu << 16; - info->mg = 0xffu << 8; - info->mb = 0xffu << 0; - info->ma = 0xffu << 24; - info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 - } else { - info->mr = 31u << 10; - info->mg = 31u << 5; - info->mb = 31u << 0; - } - } else if (compress == 3) { - info->mr = stbi__get32le(s); - info->mg = stbi__get32le(s); - info->mb = stbi__get32le(s); - // not documented, but generated by photoshop and handled by mspaint - if (info->mr == info->mg && info->mg == info->mb) { - // ?!?!? - return stbi__errpuc("bad BMP", "bad BMP"); - } - } else - return stbi__errpuc("bad BMP", "bad BMP"); - } - } else { - int i; - if (hsz != 108 && hsz != 124) - return stbi__errpuc("bad BMP", "bad BMP"); - info->mr = stbi__get32le(s); - info->mg = stbi__get32le(s); - info->mb = stbi__get32le(s); - info->ma = stbi__get32le(s); - stbi__get32le(s); // discard color space - for (i=0; i < 12; ++i) - stbi__get32le(s); // discard color space parameters - if (hsz == 124) { - stbi__get32le(s); // discard rendering intent - stbi__get32le(s); // discard offset of profile data - stbi__get32le(s); // discard size of profile data - stbi__get32le(s); // discard reserved - } - } - } - return (void *) 1; -} - - -static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - stbi_uc *out; - unsigned int mr=0,mg=0,mb=0,ma=0, all_a; - stbi_uc pal[256][4]; - int psize=0,i,j,width; - int flip_vertically, pad, target; - stbi__bmp_data info; - STBI_NOTUSED(ri); - - info.all_a = 255; - if (stbi__bmp_parse_header(s, &info) == NULL) - return NULL; // error code already set - - flip_vertically = ((int) s->img_y) > 0; - s->img_y = abs((int) s->img_y); - - mr = info.mr; - mg = info.mg; - mb = info.mb; - ma = info.ma; - all_a = info.all_a; - - if (info.hsz == 12) { - if (info.bpp < 24) - psize = (info.offset - 14 - 24) / 3; - } else { - if (info.bpp < 16) - psize = (info.offset - 14 - info.hsz) >> 2; - } - - s->img_n = ma ? 4 : 3; - if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 - target = req_comp; - else - target = s->img_n; // if they want monochrome, we'll post-convert - - // sanity-check size - if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0)) - return stbi__errpuc("too large", "Corrupt BMP"); - - out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0); - if (!out) return stbi__errpuc("outofmem", "Out of memory"); - if (info.bpp < 16) { - int z=0; - if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } - for (i=0; i < psize; ++i) { - pal[i][2] = stbi__get8(s); - pal[i][1] = stbi__get8(s); - pal[i][0] = stbi__get8(s); - if (info.hsz != 12) stbi__get8(s); - pal[i][3] = 255; - } - stbi__skip(s, info.offset - 14 - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); - if (info.bpp == 4) width = (s->img_x + 1) >> 1; - else if (info.bpp == 8) width = s->img_x; - else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } - pad = (-width)&3; - for (j=0; j < (int) s->img_y; ++j) { - for (i=0; i < (int) s->img_x; i += 2) { - int v=stbi__get8(s),v2=0; - if (info.bpp == 4) { - v2 = v & 15; - v >>= 4; - } - out[z++] = pal[v][0]; - out[z++] = pal[v][1]; - out[z++] = pal[v][2]; - if (target == 4) out[z++] = 255; - if (i+1 == (int) s->img_x) break; - v = (info.bpp == 8) ? stbi__get8(s) : v2; - out[z++] = pal[v][0]; - out[z++] = pal[v][1]; - out[z++] = pal[v][2]; - if (target == 4) out[z++] = 255; - } - stbi__skip(s, pad); - } - } else { - int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; - int z = 0; - int easy=0; - stbi__skip(s, info.offset - 14 - info.hsz); - if (info.bpp == 24) width = 3 * s->img_x; - else if (info.bpp == 16) width = 2*s->img_x; - else /* bpp = 32 and pad = 0 */ width=0; - pad = (-width) & 3; - if (info.bpp == 24) { - easy = 1; - } else if (info.bpp == 32) { - if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) - easy = 2; - } - if (!easy) { - if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } - // right shift amt to put high bit in position #7 - rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); - gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); - bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); - ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); - } - for (j=0; j < (int) s->img_y; ++j) { - if (easy) { - for (i=0; i < (int) s->img_x; ++i) { - unsigned char a; - out[z+2] = stbi__get8(s); - out[z+1] = stbi__get8(s); - out[z+0] = stbi__get8(s); - z += 3; - a = (easy == 2 ? stbi__get8(s) : 255); - all_a |= a; - if (target == 4) out[z++] = a; - } - } else { - int bpp = info.bpp; - for (i=0; i < (int) s->img_x; ++i) { - stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); - int a; - out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); - out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); - out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); - a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); - all_a |= a; - if (target == 4) out[z++] = STBI__BYTECAST(a); - } - } - stbi__skip(s, pad); - } - } - - // if alpha channel is all 0s, replace with all 255s - if (target == 4 && all_a == 0) - for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) - out[i] = 255; - - if (flip_vertically) { - stbi_uc t; - for (j=0; j < (int) s->img_y>>1; ++j) { - stbi_uc *p1 = out + j *s->img_x*target; - stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; - for (i=0; i < (int) s->img_x*target; ++i) { - t = p1[i], p1[i] = p2[i], p2[i] = t; - } - } - } - - if (req_comp && req_comp != target) { - out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); - if (out == NULL) return out; // stbi__convert_format frees input on failure - } - - *x = s->img_x; - *y = s->img_y; - if (comp) *comp = s->img_n; - return out; -} -#endif - -// Targa Truevision - TGA -// by Jonathan Dummer -#ifndef STBI_NO_TGA -// returns STBI_rgb or whatever, 0 on error -static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) -{ - // only RGB or RGBA (incl. 16bit) or grey allowed - if(is_rgb16) *is_rgb16 = 0; - switch(bits_per_pixel) { - case 8: return STBI_grey; - case 16: if(is_grey) return STBI_grey_alpha; - // else: fall-through - case 15: if(is_rgb16) *is_rgb16 = 1; - return STBI_rgb; - case 24: // fall-through - case 32: return bits_per_pixel/8; - default: return 0; - } -} - -static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) -{ - int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; - int sz, tga_colormap_type; - stbi__get8(s); // discard Offset - tga_colormap_type = stbi__get8(s); // colormap type - if( tga_colormap_type > 1 ) { - stbi__rewind(s); - return 0; // only RGB or indexed allowed - } - tga_image_type = stbi__get8(s); // image type - if ( tga_colormap_type == 1 ) { // colormapped (paletted) image - if (tga_image_type != 1 && tga_image_type != 9) { - stbi__rewind(s); - return 0; - } - stbi__skip(s,4); // skip index of first colormap entry and number of entries - sz = stbi__get8(s); // check bits per palette color entry - if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { - stbi__rewind(s); - return 0; - } - stbi__skip(s,4); // skip image x and y origin - tga_colormap_bpp = sz; - } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE - if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { - stbi__rewind(s); - return 0; // only RGB or grey allowed, +/- RLE - } - stbi__skip(s,9); // skip colormap specification and image x/y origin - tga_colormap_bpp = 0; - } - tga_w = stbi__get16le(s); - if( tga_w < 1 ) { - stbi__rewind(s); - return 0; // test width - } - tga_h = stbi__get16le(s); - if( tga_h < 1 ) { - stbi__rewind(s); - return 0; // test height - } - tga_bits_per_pixel = stbi__get8(s); // bits per pixel - stbi__get8(s); // ignore alpha bits - if (tga_colormap_bpp != 0) { - if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { - // when using a colormap, tga_bits_per_pixel is the size of the indexes - // I don't think anything but 8 or 16bit indexes makes sense - stbi__rewind(s); - return 0; - } - tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); - } else { - tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); - } - if(!tga_comp) { - stbi__rewind(s); - return 0; - } - if (x) *x = tga_w; - if (y) *y = tga_h; - if (comp) *comp = tga_comp; - return 1; // seems to have passed everything -} - -static int stbi__tga_test(stbi__context *s) -{ - int res = 0; - int sz, tga_color_type; - stbi__get8(s); // discard Offset - tga_color_type = stbi__get8(s); // color type - if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed - sz = stbi__get8(s); // image type - if ( tga_color_type == 1 ) { // colormapped (paletted) image - if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 - stbi__skip(s,4); // skip index of first colormap entry and number of entries - sz = stbi__get8(s); // check bits per palette color entry - if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; - stbi__skip(s,4); // skip image x and y origin - } else { // "normal" image w/o colormap - if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE - stbi__skip(s,9); // skip colormap specification and image x/y origin - } - if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width - if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height - sz = stbi__get8(s); // bits per pixel - if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index - if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; - - res = 1; // if we got this far, everything's good and we can return 1 instead of 0 - -errorEnd: - stbi__rewind(s); - return res; -} - -// read 16bit value and convert to 24bit RGB -static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) -{ - stbi__uint16 px = (stbi__uint16)stbi__get16le(s); - stbi__uint16 fiveBitMask = 31; - // we have 3 channels with 5bits each - int r = (px >> 10) & fiveBitMask; - int g = (px >> 5) & fiveBitMask; - int b = px & fiveBitMask; - // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later - out[0] = (stbi_uc)((r * 255)/31); - out[1] = (stbi_uc)((g * 255)/31); - out[2] = (stbi_uc)((b * 255)/31); - - // some people claim that the most significant bit might be used for alpha - // (possibly if an alpha-bit is set in the "image descriptor byte") - // but that only made 16bit test images completely translucent.. - // so let's treat all 15 and 16bit TGAs as RGB with no alpha. -} - -static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - // read in the TGA header stuff - int tga_offset = stbi__get8(s); - int tga_indexed = stbi__get8(s); - int tga_image_type = stbi__get8(s); - int tga_is_RLE = 0; - int tga_palette_start = stbi__get16le(s); - int tga_palette_len = stbi__get16le(s); - int tga_palette_bits = stbi__get8(s); - int tga_x_origin = stbi__get16le(s); - int tga_y_origin = stbi__get16le(s); - int tga_width = stbi__get16le(s); - int tga_height = stbi__get16le(s); - int tga_bits_per_pixel = stbi__get8(s); - int tga_comp, tga_rgb16=0; - int tga_inverted = stbi__get8(s); - // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) - // image data - unsigned char *tga_data; - unsigned char *tga_palette = NULL; - int i, j; - unsigned char raw_data[4] = {0}; - int RLE_count = 0; - int RLE_repeating = 0; - int read_next_pixel = 1; - STBI_NOTUSED(ri); - - // do a tiny bit of precessing - if ( tga_image_type >= 8 ) - { - tga_image_type -= 8; - tga_is_RLE = 1; - } - tga_inverted = 1 - ((tga_inverted >> 5) & 1); - - // If I'm paletted, then I'll use the number of bits from the palette - if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); - else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); - - if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency - return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); - - // tga info - *x = tga_width; - *y = tga_height; - if (comp) *comp = tga_comp; - - if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0)) - return stbi__errpuc("too large", "Corrupt TGA"); - - tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0); - if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); - - // skip to the data's starting position (offset usually = 0) - stbi__skip(s, tga_offset ); - - if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { - for (i=0; i < tga_height; ++i) { - int row = tga_inverted ? tga_height -i - 1 : i; - stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; - stbi__getn(s, tga_row, tga_width * tga_comp); - } - } else { - // do I need to load a palette? - if ( tga_indexed) - { - // any data to skip? (offset usually = 0) - stbi__skip(s, tga_palette_start ); - // load the palette - tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0); - if (!tga_palette) { - STBI_FREE(tga_data); - return stbi__errpuc("outofmem", "Out of memory"); - } - if (tga_rgb16) { - stbi_uc *pal_entry = tga_palette; - STBI_ASSERT(tga_comp == STBI_rgb); - for (i=0; i < tga_palette_len; ++i) { - stbi__tga_read_rgb16(s, pal_entry); - pal_entry += tga_comp; - } - } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { - STBI_FREE(tga_data); - STBI_FREE(tga_palette); - return stbi__errpuc("bad palette", "Corrupt TGA"); - } - } - // load the data - for (i=0; i < tga_width * tga_height; ++i) - { - // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? - if ( tga_is_RLE ) - { - if ( RLE_count == 0 ) - { - // yep, get the next byte as a RLE command - int RLE_cmd = stbi__get8(s); - RLE_count = 1 + (RLE_cmd & 127); - RLE_repeating = RLE_cmd >> 7; - read_next_pixel = 1; - } else if ( !RLE_repeating ) - { - read_next_pixel = 1; - } - } else - { - read_next_pixel = 1; - } - // OK, if I need to read a pixel, do it now - if ( read_next_pixel ) - { - // load however much data we did have - if ( tga_indexed ) - { - // read in index, then perform the lookup - int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); - if ( pal_idx >= tga_palette_len ) { - // invalid index - pal_idx = 0; - } - pal_idx *= tga_comp; - for (j = 0; j < tga_comp; ++j) { - raw_data[j] = tga_palette[pal_idx+j]; - } - } else if(tga_rgb16) { - STBI_ASSERT(tga_comp == STBI_rgb); - stbi__tga_read_rgb16(s, raw_data); - } else { - // read in the data raw - for (j = 0; j < tga_comp; ++j) { - raw_data[j] = stbi__get8(s); - } - } - // clear the reading flag for the next pixel - read_next_pixel = 0; - } // end of reading a pixel - - // copy data - for (j = 0; j < tga_comp; ++j) - tga_data[i*tga_comp+j] = raw_data[j]; - - // in case we're in RLE mode, keep counting down - --RLE_count; - } - // do I need to invert the image? - if ( tga_inverted ) - { - for (j = 0; j*2 < tga_height; ++j) - { - int index1 = j * tga_width * tga_comp; - int index2 = (tga_height - 1 - j) * tga_width * tga_comp; - for (i = tga_width * tga_comp; i > 0; --i) - { - unsigned char temp = tga_data[index1]; - tga_data[index1] = tga_data[index2]; - tga_data[index2] = temp; - ++index1; - ++index2; - } - } - } - // clear my palette, if I had one - if ( tga_palette != NULL ) - { - STBI_FREE( tga_palette ); - } - } - - // swap RGB - if the source data was RGB16, it already is in the right order - if (tga_comp >= 3 && !tga_rgb16) - { - unsigned char* tga_pixel = tga_data; - for (i=0; i < tga_width * tga_height; ++i) - { - unsigned char temp = tga_pixel[0]; - tga_pixel[0] = tga_pixel[2]; - tga_pixel[2] = temp; - tga_pixel += tga_comp; - } - } - - // convert to target component count - if (req_comp && req_comp != tga_comp) - tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); - - // the things I do to get rid of an error message, and yet keep - // Microsoft's C compilers happy... [8^( - tga_palette_start = tga_palette_len = tga_palette_bits = - tga_x_origin = tga_y_origin = 0; - // OK, done - return tga_data; -} -#endif - -// ************************************************************************************************* -// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB - -#ifndef STBI_NO_PSD -static int stbi__psd_test(stbi__context *s) -{ - int r = (stbi__get32be(s) == 0x38425053); - stbi__rewind(s); - return r; -} - -static int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount) -{ - int count, nleft, len; - - count = 0; - while ((nleft = pixelCount - count) > 0) { - len = stbi__get8(s); - if (len == 128) { - // No-op. - } else if (len < 128) { - // Copy next len+1 bytes literally. - len++; - if (len > nleft) return 0; // corrupt data - count += len; - while (len) { - *p = stbi__get8(s); - p += 4; - len--; - } - } else if (len > 128) { - stbi_uc val; - // Next -len+1 bytes in the dest are replicated from next source byte. - // (Interpret len as a negative 8-bit int.) - len = 257 - len; - if (len > nleft) return 0; // corrupt data - val = stbi__get8(s); - count += len; - while (len) { - *p = val; - p += 4; - len--; - } - } - } - - return 1; -} - -static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) -{ - int pixelCount; - int channelCount, compression; - int channel, i; - int bitdepth; - int w,h; - stbi_uc *out; - STBI_NOTUSED(ri); - - // Check identifier - if (stbi__get32be(s) != 0x38425053) // "8BPS" - return stbi__errpuc("not PSD", "Corrupt PSD image"); - - // Check file type version. - if (stbi__get16be(s) != 1) - return stbi__errpuc("wrong version", "Unsupported version of PSD image"); - - // Skip 6 reserved bytes. - stbi__skip(s, 6 ); - - // Read the number of channels (R, G, B, A, etc). - channelCount = stbi__get16be(s); - if (channelCount < 0 || channelCount > 16) - return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); - - // Read the rows and columns of the image. - h = stbi__get32be(s); - w = stbi__get32be(s); - - // Make sure the depth is 8 bits. - bitdepth = stbi__get16be(s); - if (bitdepth != 8 && bitdepth != 16) - return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); - - // Make sure the color mode is RGB. - // Valid options are: - // 0: Bitmap - // 1: Grayscale - // 2: Indexed color - // 3: RGB color - // 4: CMYK color - // 7: Multichannel - // 8: Duotone - // 9: Lab color - if (stbi__get16be(s) != 3) - return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); - - // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) - stbi__skip(s,stbi__get32be(s) ); - - // Skip the image resources. (resolution, pen tool paths, etc) - stbi__skip(s, stbi__get32be(s) ); - - // Skip the reserved data. - stbi__skip(s, stbi__get32be(s) ); - - // Find out if the data is compressed. - // Known values: - // 0: no compression - // 1: RLE compressed - compression = stbi__get16be(s); - if (compression > 1) - return stbi__errpuc("bad compression", "PSD has an unknown compression format"); - - // Check size - if (!stbi__mad3sizes_valid(4, w, h, 0)) - return stbi__errpuc("too large", "Corrupt PSD"); - - // Create the destination image. - - if (!compression && bitdepth == 16 && bpc == 16) { - out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0); - ri->bits_per_channel = 16; - } else - out = (stbi_uc *) stbi__malloc(4 * w*h); - - if (!out) return stbi__errpuc("outofmem", "Out of memory"); - pixelCount = w*h; - - // Initialize the data to zero. - //memset( out, 0, pixelCount * 4 ); - - // Finally, the image data. - if (compression) { - // RLE as used by .PSD and .TIFF - // Loop until you get the number of unpacked bytes you are expecting: - // Read the next source byte into n. - // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. - // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. - // Else if n is 128, noop. - // Endloop - - // The RLE-compressed data is preceeded by a 2-byte data count for each row in the data, - // which we're going to just skip. - stbi__skip(s, h * channelCount * 2 ); - - // Read the RLE data by channel. - for (channel = 0; channel < 4; channel++) { - stbi_uc *p; - - p = out+channel; - if (channel >= channelCount) { - // Fill this channel with default data. - for (i = 0; i < pixelCount; i++, p += 4) - *p = (channel == 3 ? 255 : 0); - } else { - // Read the RLE data. - if (!stbi__psd_decode_rle(s, p, pixelCount)) { - STBI_FREE(out); - return stbi__errpuc("corrupt", "bad RLE data"); - } - } - } - - } else { - // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) - // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image. - - // Read the data by channel. - for (channel = 0; channel < 4; channel++) { - if (channel >= channelCount) { - // Fill this channel with default data. - if (bitdepth == 16 && bpc == 16) { - stbi__uint16 *q = ((stbi__uint16 *) out) + channel; - stbi__uint16 val = channel == 3 ? 65535 : 0; - for (i = 0; i < pixelCount; i++, q += 4) - *q = val; - } else { - stbi_uc *p = out+channel; - stbi_uc val = channel == 3 ? 255 : 0; - for (i = 0; i < pixelCount; i++, p += 4) - *p = val; - } - } else { - if (ri->bits_per_channel == 16) { // output bpc - stbi__uint16 *q = ((stbi__uint16 *) out) + channel; - for (i = 0; i < pixelCount; i++, q += 4) - *q = (stbi__uint16) stbi__get16be(s); - } else { - stbi_uc *p = out+channel; - if (bitdepth == 16) { // input bpc - for (i = 0; i < pixelCount; i++, p += 4) - *p = (stbi_uc) (stbi__get16be(s) >> 8); - } else { - for (i = 0; i < pixelCount; i++, p += 4) - *p = stbi__get8(s); - } - } - } - } - } - - // remove weird white matte from PSD - if (channelCount >= 4) { - if (ri->bits_per_channel == 16) { - for (i=0; i < w*h; ++i) { - stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i; - if (pixel[3] != 0 && pixel[3] != 65535) { - float a = pixel[3] / 65535.0f; - float ra = 1.0f / a; - float inv_a = 65535.0f * (1 - ra); - pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a); - pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a); - pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a); - } - } - } else { - for (i=0; i < w*h; ++i) { - unsigned char *pixel = out + 4*i; - if (pixel[3] != 0 && pixel[3] != 255) { - float a = pixel[3] / 255.0f; - float ra = 1.0f / a; - float inv_a = 255.0f * (1 - ra); - pixel[0] = (unsigned char) (pixel[0]*ra + inv_a); - pixel[1] = (unsigned char) (pixel[1]*ra + inv_a); - pixel[2] = (unsigned char) (pixel[2]*ra + inv_a); - } - } - } - } - - // convert to desired output format - if (req_comp && req_comp != 4) { - if (ri->bits_per_channel == 16) - out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h); - else - out = stbi__convert_format(out, 4, req_comp, w, h); - if (out == NULL) return out; // stbi__convert_format frees input on failure - } - - if (comp) *comp = 4; - *y = h; - *x = w; - - return out; -} -#endif - -// ************************************************************************************************* -// Softimage PIC loader -// by Tom Seddon -// -// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format -// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ - -#ifndef STBI_NO_PIC -static int stbi__pic_is4(stbi__context *s,const char *str) -{ - int i; - for (i=0; i<4; ++i) - if (stbi__get8(s) != (stbi_uc)str[i]) - return 0; - - return 1; -} - -static int stbi__pic_test_core(stbi__context *s) -{ - int i; - - if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) - return 0; - - for(i=0;i<84;++i) - stbi__get8(s); - - if (!stbi__pic_is4(s,"PICT")) - return 0; - - return 1; -} - -typedef struct -{ - stbi_uc size,type,channel; -} stbi__pic_packet; - -static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) -{ - int mask=0x80, i; - - for (i=0; i<4; ++i, mask>>=1) { - if (channel & mask) { - if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); - dest[i]=stbi__get8(s); - } - } - - return dest; -} - -static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) -{ - int mask=0x80,i; - - for (i=0;i<4; ++i, mask>>=1) - if (channel&mask) - dest[i]=src[i]; -} - -static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) -{ - int act_comp=0,num_packets=0,y,chained; - stbi__pic_packet packets[10]; - - // this will (should...) cater for even some bizarre stuff like having data - // for the same channel in multiple packets. - do { - stbi__pic_packet *packet; - - if (num_packets==sizeof(packets)/sizeof(packets[0])) - return stbi__errpuc("bad format","too many packets"); - - packet = &packets[num_packets++]; - - chained = stbi__get8(s); - packet->size = stbi__get8(s); - packet->type = stbi__get8(s); - packet->channel = stbi__get8(s); - - act_comp |= packet->channel; - - if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); - if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); - } while (chained); - - *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? - - for(y=0; ytype) { - default: - return stbi__errpuc("bad format","packet has bad compression type"); - - case 0: {//uncompressed - int x; - - for(x=0;xchannel,dest)) - return 0; - break; - } - - case 1://Pure RLE - { - int left=width, i; - - while (left>0) { - stbi_uc count,value[4]; - - count=stbi__get8(s); - if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); - - if (count > left) - count = (stbi_uc) left; - - if (!stbi__readval(s,packet->channel,value)) return 0; - - for(i=0; ichannel,dest,value); - left -= count; - } - } - break; - - case 2: {//Mixed RLE - int left=width; - while (left>0) { - int count = stbi__get8(s), i; - if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); - - if (count >= 128) { // Repeated - stbi_uc value[4]; - - if (count==128) - count = stbi__get16be(s); - else - count -= 127; - if (count > left) - return stbi__errpuc("bad file","scanline overrun"); - - if (!stbi__readval(s,packet->channel,value)) - return 0; - - for(i=0;ichannel,dest,value); - } else { // Raw - ++count; - if (count>left) return stbi__errpuc("bad file","scanline overrun"); - - for(i=0;ichannel,dest)) - return 0; - } - left-=count; - } - break; - } - } - } - } - - return result; -} - -static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri) -{ - stbi_uc *result; - int i, x,y, internal_comp; - STBI_NOTUSED(ri); - - if (!comp) comp = &internal_comp; - - for (i=0; i<92; ++i) - stbi__get8(s); - - x = stbi__get16be(s); - y = stbi__get16be(s); - if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); - if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode"); - - stbi__get32be(s); //skip `ratio' - stbi__get16be(s); //skip `fields' - stbi__get16be(s); //skip `pad' - - // intermediate buffer is RGBA - result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0); - memset(result, 0xff, x*y*4); - - if (!stbi__pic_load_core(s,x,y,comp, result)) { - STBI_FREE(result); - result=0; - } - *px = x; - *py = y; - if (req_comp == 0) req_comp = *comp; - result=stbi__convert_format(result,4,req_comp,x,y); - - return result; -} - -static int stbi__pic_test(stbi__context *s) -{ - int r = stbi__pic_test_core(s); - stbi__rewind(s); - return r; -} -#endif - -// ************************************************************************************************* -// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb - -#ifndef STBI_NO_GIF -typedef struct -{ - stbi__int16 prefix; - stbi_uc first; - stbi_uc suffix; -} stbi__gif_lzw; - -typedef struct -{ - int w,h; - stbi_uc *out, *old_out; // output buffer (always 4 components) - int flags, bgindex, ratio, transparent, eflags, delay; - stbi_uc pal[256][4]; - stbi_uc lpal[256][4]; - stbi__gif_lzw codes[4096]; - stbi_uc *color_table; - int parse, step; - int lflags; - int start_x, start_y; - int max_x, max_y; - int cur_x, cur_y; - int line_size; -} stbi__gif; - -static int stbi__gif_test_raw(stbi__context *s) -{ - int sz; - if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; - sz = stbi__get8(s); - if (sz != '9' && sz != '7') return 0; - if (stbi__get8(s) != 'a') return 0; - return 1; -} - -static int stbi__gif_test(stbi__context *s) -{ - int r = stbi__gif_test_raw(s); - stbi__rewind(s); - return r; -} - -static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) -{ - int i; - for (i=0; i < num_entries; ++i) { - pal[i][2] = stbi__get8(s); - pal[i][1] = stbi__get8(s); - pal[i][0] = stbi__get8(s); - pal[i][3] = transp == i ? 0 : 255; - } -} - -static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) -{ - stbi_uc version; - if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') - return stbi__err("not GIF", "Corrupt GIF"); - - version = stbi__get8(s); - if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); - if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); - - stbi__g_failure_reason = ""; - g->w = stbi__get16le(s); - g->h = stbi__get16le(s); - g->flags = stbi__get8(s); - g->bgindex = stbi__get8(s); - g->ratio = stbi__get8(s); - g->transparent = -1; - - if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments - - if (is_info) return 1; - - if (g->flags & 0x80) - stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); - - return 1; -} - -static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) -{ - stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); - if (!stbi__gif_header(s, g, comp, 1)) { - STBI_FREE(g); - stbi__rewind( s ); - return 0; - } - if (x) *x = g->w; - if (y) *y = g->h; - STBI_FREE(g); - return 1; -} - -static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) -{ - stbi_uc *p, *c; - - // recurse to decode the prefixes, since the linked-list is backwards, - // and working backwards through an interleaved image would be nasty - if (g->codes[code].prefix >= 0) - stbi__out_gif_code(g, g->codes[code].prefix); - - if (g->cur_y >= g->max_y) return; - - p = &g->out[g->cur_x + g->cur_y]; - c = &g->color_table[g->codes[code].suffix * 4]; - - if (c[3] >= 128) { - p[0] = c[2]; - p[1] = c[1]; - p[2] = c[0]; - p[3] = c[3]; - } - g->cur_x += 4; - - if (g->cur_x >= g->max_x) { - g->cur_x = g->start_x; - g->cur_y += g->step; - - while (g->cur_y >= g->max_y && g->parse > 0) { - g->step = (1 << g->parse) * g->line_size; - g->cur_y = g->start_y + (g->step >> 1); - --g->parse; - } - } -} - -static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) -{ - stbi_uc lzw_cs; - stbi__int32 len, init_code; - stbi__uint32 first; - stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; - stbi__gif_lzw *p; - - lzw_cs = stbi__get8(s); - if (lzw_cs > 12) return NULL; - clear = 1 << lzw_cs; - first = 1; - codesize = lzw_cs + 1; - codemask = (1 << codesize) - 1; - bits = 0; - valid_bits = 0; - for (init_code = 0; init_code < clear; init_code++) { - g->codes[init_code].prefix = -1; - g->codes[init_code].first = (stbi_uc) init_code; - g->codes[init_code].suffix = (stbi_uc) init_code; - } - - // support no starting clear code - avail = clear+2; - oldcode = -1; - - len = 0; - for(;;) { - if (valid_bits < codesize) { - if (len == 0) { - len = stbi__get8(s); // start new block - if (len == 0) - return g->out; - } - --len; - bits |= (stbi__int32) stbi__get8(s) << valid_bits; - valid_bits += 8; - } else { - stbi__int32 code = bits & codemask; - bits >>= codesize; - valid_bits -= codesize; - // @OPTIMIZE: is there some way we can accelerate the non-clear path? - if (code == clear) { // clear code - codesize = lzw_cs + 1; - codemask = (1 << codesize) - 1; - avail = clear + 2; - oldcode = -1; - first = 0; - } else if (code == clear + 1) { // end of stream code - stbi__skip(s, len); - while ((len = stbi__get8(s)) > 0) - stbi__skip(s,len); - return g->out; - } else if (code <= avail) { - if (first) return stbi__errpuc("no clear code", "Corrupt GIF"); - - if (oldcode >= 0) { - p = &g->codes[avail++]; - if (avail > 4096) return stbi__errpuc("too many codes", "Corrupt GIF"); - p->prefix = (stbi__int16) oldcode; - p->first = g->codes[oldcode].first; - p->suffix = (code == avail) ? p->first : g->codes[code].first; - } else if (code == avail) - return stbi__errpuc("illegal code in raster", "Corrupt GIF"); - - stbi__out_gif_code(g, (stbi__uint16) code); - - if ((avail & codemask) == 0 && avail <= 0x0FFF) { - codesize++; - codemask = (1 << codesize) - 1; - } - - oldcode = code; - } else { - return stbi__errpuc("illegal code in raster", "Corrupt GIF"); - } - } - } -} - -static void stbi__fill_gif_background(stbi__gif *g, int x0, int y0, int x1, int y1) -{ - int x, y; - stbi_uc *c = g->pal[g->bgindex]; - for (y = y0; y < y1; y += 4 * g->w) { - for (x = x0; x < x1; x += 4) { - stbi_uc *p = &g->out[y + x]; - p[0] = c[2]; - p[1] = c[1]; - p[2] = c[0]; - p[3] = 0; - } - } -} - -// this function is designed to support animated gifs, although stb_image doesn't support it -static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp) -{ - int i; - stbi_uc *prev_out = 0; - - if (g->out == 0 && !stbi__gif_header(s, g, comp,0)) - return 0; // stbi__g_failure_reason set by stbi__gif_header - - if (!stbi__mad3sizes_valid(g->w, g->h, 4, 0)) - return stbi__errpuc("too large", "GIF too large"); - - prev_out = g->out; - g->out = (stbi_uc *) stbi__malloc_mad3(4, g->w, g->h, 0); - if (g->out == 0) return stbi__errpuc("outofmem", "Out of memory"); - - switch ((g->eflags & 0x1C) >> 2) { - case 0: // unspecified (also always used on 1st frame) - stbi__fill_gif_background(g, 0, 0, 4 * g->w, 4 * g->w * g->h); - break; - case 1: // do not dispose - if (prev_out) memcpy(g->out, prev_out, 4 * g->w * g->h); - g->old_out = prev_out; - break; - case 2: // dispose to background - if (prev_out) memcpy(g->out, prev_out, 4 * g->w * g->h); - stbi__fill_gif_background(g, g->start_x, g->start_y, g->max_x, g->max_y); - break; - case 3: // dispose to previous - if (g->old_out) { - for (i = g->start_y; i < g->max_y; i += 4 * g->w) - memcpy(&g->out[i + g->start_x], &g->old_out[i + g->start_x], g->max_x - g->start_x); - } - break; - } - - for (;;) { - switch (stbi__get8(s)) { - case 0x2C: /* Image Descriptor */ - { - int prev_trans = -1; - stbi__int32 x, y, w, h; - stbi_uc *o; - - x = stbi__get16le(s); - y = stbi__get16le(s); - w = stbi__get16le(s); - h = stbi__get16le(s); - if (((x + w) > (g->w)) || ((y + h) > (g->h))) - return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); - - g->line_size = g->w * 4; - g->start_x = x * 4; - g->start_y = y * g->line_size; - g->max_x = g->start_x + w * 4; - g->max_y = g->start_y + h * g->line_size; - g->cur_x = g->start_x; - g->cur_y = g->start_y; - - g->lflags = stbi__get8(s); - - if (g->lflags & 0x40) { - g->step = 8 * g->line_size; // first interlaced spacing - g->parse = 3; - } else { - g->step = g->line_size; - g->parse = 0; - } - - if (g->lflags & 0x80) { - stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); - g->color_table = (stbi_uc *) g->lpal; - } else if (g->flags & 0x80) { - if (g->transparent >= 0 && (g->eflags & 0x01)) { - prev_trans = g->pal[g->transparent][3]; - g->pal[g->transparent][3] = 0; - } - g->color_table = (stbi_uc *) g->pal; - } else - return stbi__errpuc("missing color table", "Corrupt GIF"); - - o = stbi__process_gif_raster(s, g); - if (o == NULL) return NULL; - - if (prev_trans != -1) - g->pal[g->transparent][3] = (stbi_uc) prev_trans; - - return o; - } - - case 0x21: // Comment Extension. - { - int len; - if (stbi__get8(s) == 0xF9) { // Graphic Control Extension. - len = stbi__get8(s); - if (len == 4) { - g->eflags = stbi__get8(s); - g->delay = stbi__get16le(s); - g->transparent = stbi__get8(s); - } else { - stbi__skip(s, len); - break; - } - } - while ((len = stbi__get8(s)) != 0) - stbi__skip(s, len); - break; - } - - case 0x3B: // gif stream termination code - return (stbi_uc *) s; // using '1' causes warning on some compilers - - default: - return stbi__errpuc("unknown code", "Corrupt GIF"); - } - } - - STBI_NOTUSED(req_comp); -} - -static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - stbi_uc *u = 0; - stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); - memset(g, 0, sizeof(*g)); - STBI_NOTUSED(ri); - - u = stbi__gif_load_next(s, g, comp, req_comp); - if (u == (stbi_uc *) s) u = 0; // end of animated gif marker - if (u) { - *x = g->w; - *y = g->h; - if (req_comp && req_comp != 4) - u = stbi__convert_format(u, 4, req_comp, g->w, g->h); - } - else if (g->out) - STBI_FREE(g->out); - STBI_FREE(g); - return u; -} - -static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) -{ - return stbi__gif_info_raw(s,x,y,comp); -} -#endif - -// ************************************************************************************************* -// Radiance RGBE HDR loader -// originally by Nicolas Schulz -#ifndef STBI_NO_HDR -static int stbi__hdr_test_core(stbi__context *s, const char *signature) -{ - int i; - for (i=0; signature[i]; ++i) - if (stbi__get8(s) != signature[i]) - return 0; - stbi__rewind(s); - return 1; -} - -static int stbi__hdr_test(stbi__context* s) -{ - int r = stbi__hdr_test_core(s, "#?RADIANCE\n"); - stbi__rewind(s); - if(!r) { - r = stbi__hdr_test_core(s, "#?RGBE\n"); - stbi__rewind(s); - } - return r; -} - -#define STBI__HDR_BUFLEN 1024 -static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) -{ - int len=0; - char c = '\0'; - - c = (char) stbi__get8(z); - - while (!stbi__at_eof(z) && c != '\n') { - buffer[len++] = c; - if (len == STBI__HDR_BUFLEN-1) { - // flush to end of line - while (!stbi__at_eof(z) && stbi__get8(z) != '\n') - ; - break; - } - c = (char) stbi__get8(z); - } - - buffer[len] = 0; - return buffer; -} - -static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) -{ - if ( input[3] != 0 ) { - float f1; - // Exponent - f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); - if (req_comp <= 2) - output[0] = (input[0] + input[1] + input[2]) * f1 / 3; - else { - output[0] = input[0] * f1; - output[1] = input[1] * f1; - output[2] = input[2] * f1; - } - if (req_comp == 2) output[1] = 1; - if (req_comp == 4) output[3] = 1; - } else { - switch (req_comp) { - case 4: output[3] = 1; /* fallthrough */ - case 3: output[0] = output[1] = output[2] = 0; - break; - case 2: output[1] = 1; /* fallthrough */ - case 1: output[0] = 0; - break; - } - } -} - -static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - char buffer[STBI__HDR_BUFLEN]; - char *token; - int valid = 0; - int width, height; - stbi_uc *scanline; - float *hdr_data; - int len; - unsigned char count, value; - int i, j, k, c1,c2, z; - const char *headerToken; - STBI_NOTUSED(ri); - - // Check identifier - headerToken = stbi__hdr_gettoken(s,buffer); - if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0) - return stbi__errpf("not HDR", "Corrupt HDR image"); - - // Parse header - for(;;) { - token = stbi__hdr_gettoken(s,buffer); - if (token[0] == 0) break; - if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; - } - - if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); - - // Parse width and height - // can't use sscanf() if we're not using stdio! - token = stbi__hdr_gettoken(s,buffer); - if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); - token += 3; - height = (int) strtol(token, &token, 10); - while (*token == ' ') ++token; - if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); - token += 3; - width = (int) strtol(token, NULL, 10); - - *x = width; - *y = height; - - if (comp) *comp = 3; - if (req_comp == 0) req_comp = 3; - - if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0)) - return stbi__errpf("too large", "HDR image is too large"); - - // Read data - hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0); - if (!hdr_data) - return stbi__errpf("outofmem", "Out of memory"); - - // Load image data - // image data is stored as some number of sca - if ( width < 8 || width >= 32768) { - // Read flat data - for (j=0; j < height; ++j) { - for (i=0; i < width; ++i) { - stbi_uc rgbe[4]; - main_decode_loop: - stbi__getn(s, rgbe, 4); - stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); - } - } - } else { - // Read RLE-encoded data - scanline = NULL; - - for (j = 0; j < height; ++j) { - c1 = stbi__get8(s); - c2 = stbi__get8(s); - len = stbi__get8(s); - if (c1 != 2 || c2 != 2 || (len & 0x80)) { - // not run-length encoded, so we have to actually use THIS data as a decoded - // pixel (note this can't be a valid pixel--one of RGB must be >= 128) - stbi_uc rgbe[4]; - rgbe[0] = (stbi_uc) c1; - rgbe[1] = (stbi_uc) c2; - rgbe[2] = (stbi_uc) len; - rgbe[3] = (stbi_uc) stbi__get8(s); - stbi__hdr_convert(hdr_data, rgbe, req_comp); - i = 1; - j = 0; - STBI_FREE(scanline); - goto main_decode_loop; // yes, this makes no sense - } - len <<= 8; - len |= stbi__get8(s); - if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } - if (scanline == NULL) { - scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0); - if (!scanline) { - STBI_FREE(hdr_data); - return stbi__errpf("outofmem", "Out of memory"); - } - } - - for (k = 0; k < 4; ++k) { - int nleft; - i = 0; - while ((nleft = width - i) > 0) { - count = stbi__get8(s); - if (count > 128) { - // Run - value = stbi__get8(s); - count -= 128; - if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } - for (z = 0; z < count; ++z) - scanline[i++ * 4 + k] = value; - } else { - // Dump - if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } - for (z = 0; z < count; ++z) - scanline[i++ * 4 + k] = stbi__get8(s); - } - } - } - for (i=0; i < width; ++i) - stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); - } - if (scanline) - STBI_FREE(scanline); - } - - return hdr_data; -} - -static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) -{ - char buffer[STBI__HDR_BUFLEN]; - char *token; - int valid = 0; - int dummy; - - if (!x) x = &dummy; - if (!y) y = &dummy; - if (!comp) comp = &dummy; - - if (stbi__hdr_test(s) == 0) { - stbi__rewind( s ); - return 0; - } - - for(;;) { - token = stbi__hdr_gettoken(s,buffer); - if (token[0] == 0) break; - if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; - } - - if (!valid) { - stbi__rewind( s ); - return 0; - } - token = stbi__hdr_gettoken(s,buffer); - if (strncmp(token, "-Y ", 3)) { - stbi__rewind( s ); - return 0; - } - token += 3; - *y = (int) strtol(token, &token, 10); - while (*token == ' ') ++token; - if (strncmp(token, "+X ", 3)) { - stbi__rewind( s ); - return 0; - } - token += 3; - *x = (int) strtol(token, NULL, 10); - *comp = 3; - return 1; -} -#endif // STBI_NO_HDR - -#ifndef STBI_NO_BMP -static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) -{ - void *p; - stbi__bmp_data info; - - info.all_a = 255; - p = stbi__bmp_parse_header(s, &info); - stbi__rewind( s ); - if (p == NULL) - return 0; - if (x) *x = s->img_x; - if (y) *y = s->img_y; - if (comp) *comp = info.ma ? 4 : 3; - return 1; -} -#endif - -#ifndef STBI_NO_PSD -static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) -{ - int channelCount, dummy; - if (!x) x = &dummy; - if (!y) y = &dummy; - if (!comp) comp = &dummy; - if (stbi__get32be(s) != 0x38425053) { - stbi__rewind( s ); - return 0; - } - if (stbi__get16be(s) != 1) { - stbi__rewind( s ); - return 0; - } - stbi__skip(s, 6); - channelCount = stbi__get16be(s); - if (channelCount < 0 || channelCount > 16) { - stbi__rewind( s ); - return 0; - } - *y = stbi__get32be(s); - *x = stbi__get32be(s); - if (stbi__get16be(s) != 8) { - stbi__rewind( s ); - return 0; - } - if (stbi__get16be(s) != 3) { - stbi__rewind( s ); - return 0; - } - *comp = 4; - return 1; -} -#endif - -#ifndef STBI_NO_PIC -static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) -{ - int act_comp=0,num_packets=0,chained,dummy; - stbi__pic_packet packets[10]; - - if (!x) x = &dummy; - if (!y) y = &dummy; - if (!comp) comp = &dummy; - - if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { - stbi__rewind(s); - return 0; - } - - stbi__skip(s, 88); - - *x = stbi__get16be(s); - *y = stbi__get16be(s); - if (stbi__at_eof(s)) { - stbi__rewind( s); - return 0; - } - if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { - stbi__rewind( s ); - return 0; - } - - stbi__skip(s, 8); - - do { - stbi__pic_packet *packet; - - if (num_packets==sizeof(packets)/sizeof(packets[0])) - return 0; - - packet = &packets[num_packets++]; - chained = stbi__get8(s); - packet->size = stbi__get8(s); - packet->type = stbi__get8(s); - packet->channel = stbi__get8(s); - act_comp |= packet->channel; - - if (stbi__at_eof(s)) { - stbi__rewind( s ); - return 0; - } - if (packet->size != 8) { - stbi__rewind( s ); - return 0; - } - } while (chained); - - *comp = (act_comp & 0x10 ? 4 : 3); - - return 1; -} -#endif - -// ************************************************************************************************* -// Portable Gray Map and Portable Pixel Map loader -// by Ken Miller -// -// PGM: http://netpbm.sourceforge.net/doc/pgm.html -// PPM: http://netpbm.sourceforge.net/doc/ppm.html -// -// Known limitations: -// Does not support comments in the header section -// Does not support ASCII image data (formats P2 and P3) -// Does not support 16-bit-per-channel - -#ifndef STBI_NO_PNM - -static int stbi__pnm_test(stbi__context *s) -{ - char p, t; - p = (char) stbi__get8(s); - t = (char) stbi__get8(s); - if (p != 'P' || (t != '5' && t != '6')) { - stbi__rewind( s ); - return 0; - } - return 1; -} - -static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - stbi_uc *out; - STBI_NOTUSED(ri); - - if (!stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n)) - return 0; - - *x = s->img_x; - *y = s->img_y; - if (comp) *comp = s->img_n; - - if (!stbi__mad3sizes_valid(s->img_n, s->img_x, s->img_y, 0)) - return stbi__errpuc("too large", "PNM too large"); - - out = (stbi_uc *) stbi__malloc_mad3(s->img_n, s->img_x, s->img_y, 0); - if (!out) return stbi__errpuc("outofmem", "Out of memory"); - stbi__getn(s, out, s->img_n * s->img_x * s->img_y); - - if (req_comp && req_comp != s->img_n) { - out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); - if (out == NULL) return out; // stbi__convert_format frees input on failure - } - return out; -} - -static int stbi__pnm_isspace(char c) -{ - return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; -} - -static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) -{ - for (;;) { - while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) - *c = (char) stbi__get8(s); - - if (stbi__at_eof(s) || *c != '#') - break; - - while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) - *c = (char) stbi__get8(s); - } -} - -static int stbi__pnm_isdigit(char c) -{ - return c >= '0' && c <= '9'; -} - -static int stbi__pnm_getinteger(stbi__context *s, char *c) -{ - int value = 0; - - while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { - value = value*10 + (*c - '0'); - *c = (char) stbi__get8(s); - } - - return value; -} - -static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) -{ - int maxv, dummy; - char c, p, t; - - if (!x) x = &dummy; - if (!y) y = &dummy; - if (!comp) comp = &dummy; - - stbi__rewind(s); - - // Get identifier - p = (char) stbi__get8(s); - t = (char) stbi__get8(s); - if (p != 'P' || (t != '5' && t != '6')) { - stbi__rewind(s); - return 0; - } - - *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm - - c = (char) stbi__get8(s); - stbi__pnm_skip_whitespace(s, &c); - - *x = stbi__pnm_getinteger(s, &c); // read width - stbi__pnm_skip_whitespace(s, &c); - - *y = stbi__pnm_getinteger(s, &c); // read height - stbi__pnm_skip_whitespace(s, &c); - - maxv = stbi__pnm_getinteger(s, &c); // read max value - - if (maxv > 255) - return stbi__err("max value > 255", "PPM image not 8-bit"); - else - return 1; -} -#endif - -static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) -{ - #ifndef STBI_NO_JPEG - if (stbi__jpeg_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_PNG - if (stbi__png_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_GIF - if (stbi__gif_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_BMP - if (stbi__bmp_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_PSD - if (stbi__psd_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_PIC - if (stbi__pic_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_PNM - if (stbi__pnm_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_DDS - if (stbi__dds_info(s, x, y, comp, NULL)) return 1; - #endif - - #ifndef STBI_NO_PVR - if (stbi__pvr_info(s, x, y, comp, NULL)) return 1; - #endif - - #ifndef STBI_NO_PKM - if (stbi__pkm_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_HDR - if (stbi__hdr_info(s, x, y, comp)) return 1; - #endif - - // test tga last because it's a crappy test! - #ifndef STBI_NO_TGA - if (stbi__tga_info(s, x, y, comp)) - return 1; - #endif - return stbi__err("unknown image type", "Image not of any known type, or corrupt"); -} - -#ifndef STBI_NO_STDIO -STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) -{ - FILE *f = stbi__fopen(filename, "rb"); - int result; - if (!f) return stbi__err("can't fopen", "Unable to open file"); - result = stbi_info_from_file(f, x, y, comp); - fclose(f); - return result; -} - -STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) -{ - int r; - stbi__context s; - long pos = ftell(f); - stbi__start_file(&s, f); - r = stbi__info_main(&s,x,y,comp); - fseek(f,pos,SEEK_SET); - return r; -} -#endif // !STBI_NO_STDIO - -STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) -{ - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__info_main(&s,x,y,comp); -} - -STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); - return stbi__info_main(&s,x,y,comp); -} - -// add in my DDS loading support -#ifndef STBI_NO_DDS -#include "stbi_DDS_c.h" -#endif - -// add in my pvr loading support -#ifndef STBI_NO_PVR -#include "stbi_pvr_c.h" -#endif - -// add in my pkm ( ETC1 ) loading support -#ifndef STBI_NO_PKM -#include "stbi_pkm_c.h" -#endif - -#ifndef STBI_NO_EXT -#include "stbi_ext_c.h" -#endif - -#endif // STB_IMAGE_IMPLEMENTATION - -/* - revision history: - 2.15 (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode; - warning fixes; disable run-time SSE detection on gcc; - uniform handling of optional "return" values; - thread-safe initialization of zlib tables - 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs - 2.13 (2016-11-29) add 16-bit API, only supported for PNG right now - 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes - 2.11 (2016-04-02) allocate large structures on the stack - remove white matting for transparent PSD - fix reported channel count for PNG & BMP - re-enable SSE2 in non-gcc 64-bit - support RGB-formatted JPEG - read 16-bit PNGs (only as 8-bit) - 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED - 2.09 (2016-01-16) allow comments in PNM files - 16-bit-per-pixel TGA (not bit-per-component) - info() for TGA could break due to .hdr handling - info() for BMP to shares code instead of sloppy parse - can use STBI_REALLOC_SIZED if allocator doesn't support realloc - code cleanup - 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA - 2.07 (2015-09-13) fix compiler warnings - partial animated GIF support - limited 16-bpc PSD support - #ifdef unused functions - bug with < 92 byte PIC,PNM,HDR,TGA - 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value - 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning - 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit - 2.03 (2015-04-12) extra corruption checking (mmozeiko) - stbi_set_flip_vertically_on_load (nguillemot) - fix NEON support; fix mingw support - 2.02 (2015-01-19) fix incorrect assert, fix warning - 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 - 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG - 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) - progressive JPEG (stb) - PGM/PPM support (Ken Miller) - STBI_MALLOC,STBI_REALLOC,STBI_FREE - GIF bugfix -- seemingly never worked - STBI_NO_*, STBI_ONLY_* - 1.48 (2014-12-14) fix incorrectly-named assert() - 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) - optimize PNG (ryg) - fix bug in interlaced PNG with user-specified channel count (stb) - 1.46 (2014-08-26) - fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG - 1.45 (2014-08-16) - fix MSVC-ARM internal compiler error by wrapping malloc - 1.44 (2014-08-07) - various warning fixes from Ronny Chevalier - 1.43 (2014-07-15) - fix MSVC-only compiler problem in code changed in 1.42 - 1.42 (2014-07-09) - don't define _CRT_SECURE_NO_WARNINGS (affects user code) - fixes to stbi__cleanup_jpeg path - added STBI_ASSERT to avoid requiring assert.h - 1.41 (2014-06-25) - fix search&replace from 1.36 that messed up comments/error messages - 1.40 (2014-06-22) - fix gcc struct-initialization warning - 1.39 (2014-06-15) - fix to TGA optimization when req_comp != number of components in TGA; - fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) - add support for BMP version 5 (more ignored fields) - 1.38 (2014-06-06) - suppress MSVC warnings on integer casts truncating values - fix accidental rename of 'skip' field of I/O - 1.37 (2014-06-04) - remove duplicate typedef - 1.36 (2014-06-03) - convert to header file single-file library - if de-iphone isn't set, load iphone images color-swapped instead of returning NULL - 1.35 (2014-05-27) - various warnings - fix broken STBI_SIMD path - fix bug where stbi_load_from_file no longer left file pointer in correct place - fix broken non-easy path for 32-bit BMP (possibly never used) - TGA optimization by Arseny Kapoulkine - 1.34 (unknown) - use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case - 1.33 (2011-07-14) - make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements - 1.32 (2011-07-13) - support for "info" function for all supported filetypes (SpartanJ) - 1.31 (2011-06-20) - a few more leak fixes, bug in PNG handling (SpartanJ) - 1.30 (2011-06-11) - added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) - removed deprecated format-specific test/load functions - removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway - error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) - fix inefficiency in decoding 32-bit BMP (David Woo) - 1.29 (2010-08-16) - various warning fixes from Aurelien Pocheville - 1.28 (2010-08-01) - fix bug in GIF palette transparency (SpartanJ) - 1.27 (2010-08-01) - cast-to-stbi_uc to fix warnings - 1.26 (2010-07-24) - fix bug in file buffering for PNG reported by SpartanJ - 1.25 (2010-07-17) - refix trans_data warning (Won Chun) - 1.24 (2010-07-12) - perf improvements reading from files on platforms with lock-heavy fgetc() - minor perf improvements for jpeg - deprecated type-specific functions so we'll get feedback if they're needed - attempt to fix trans_data warning (Won Chun) - 1.23 fixed bug in iPhone support - 1.22 (2010-07-10) - removed image *writing* support - stbi_info support from Jetro Lauha - GIF support from Jean-Marc Lienher - iPhone PNG-extensions from James Brown - warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) - 1.21 fix use of 'stbi_uc' in header (reported by jon blow) - 1.20 added support for Softimage PIC, by Tom Seddon - 1.19 bug in interlaced PNG corruption check (found by ryg) - 1.18 (2008-08-02) - fix a threading bug (local mutable static) - 1.17 support interlaced PNG - 1.16 major bugfix - stbi__convert_format converted one too many pixels - 1.15 initialize some fields for thread safety - 1.14 fix threadsafe conversion bug - header-file-only version (#define STBI_HEADER_FILE_ONLY before including) - 1.13 threadsafe - 1.12 const qualifiers in the API - 1.11 Support installable IDCT, colorspace conversion routines - 1.10 Fixes for 64-bit (don't use "unsigned long") - optimized upsampling by Fabian "ryg" Giesen - 1.09 Fix format-conversion for PSD code (bad global variables!) - 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz - 1.07 attempt to fix C++ warning/errors again - 1.06 attempt to fix C++ warning/errors again - 1.05 fix TGA loading to return correct *comp and use good luminance calc - 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free - 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR - 1.02 support for (subset of) HDR files, float interface for preferred access to them - 1.01 fix bug: possible bug in handling right-side up bmps... not sure - fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all - 1.00 interface to zlib that skips zlib header - 0.99 correct handling of alpha in palette - 0.98 TGA loader by lonesock; dynamically add loaders (untested) - 0.97 jpeg errors on too large a file; also catch another malloc failure - 0.96 fix detection of invalid v value - particleman@mollyrocket forum - 0.95 during header scan, seek to markers in case of padding - 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same - 0.93 handle jpegtran output; verbose errors - 0.92 read 4,8,16,24,32-bit BMP files of several formats - 0.91 output 24-bit Windows 3.0 BMP files - 0.90 fix a few more warnings; bump version number to approach 1.0 - 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd - 0.60 fix compiling as c++ - 0.59 fix warnings: merge Dave Moore's -Wall fixes - 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian - 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available - 0.56 fix bug: zlib uncompressed mode len vs. nlen - 0.55 fix bug: restart_interval not initialized to 0 - 0.54 allow NULL for 'int *comp' - 0.53 fix bug in png 3->4; speedup png decoding - 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments - 0.51 obey req_comp requests, 1-component jpegs return as 1-component, - on 'test' only check type, not whether we support this variant - 0.50 (2006-11-19) - first released version -*/ - - -/* ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright (c) 2017 Sean Barrett -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain (www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- -*/ diff --git a/lib/SOIL2/stb_image_write.h b/lib/SOIL2/stb_image_write.h deleted file mode 100644 index df623393d..000000000 --- a/lib/SOIL2/stb_image_write.h +++ /dev/null @@ -1,1092 +0,0 @@ -/* stb_image_write - v1.05 - public domain - http://nothings.org/stb/stb_image_write.h - writes out PNG/BMP/TGA images to C stdio - Sean Barrett 2010-2015 - no warranty implied; use at your own risk - - Before #including, - - #define STB_IMAGE_WRITE_IMPLEMENTATION - - in the file that you want to have the implementation. - - Will probably not work correctly with strict-aliasing optimizations. - -ABOUT: - - This header file is a library for writing images to C stdio. It could be - adapted to write to memory or a general streaming interface; let me know. - - The PNG output is not optimal; it is 20-50% larger than the file - written by a decent optimizing implementation. This library is designed - for source code compactness and simplicity, not optimal image file size - or run-time performance. - -BUILDING: - - You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h. - You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace - malloc,realloc,free. - You can define STBIW_MEMMOVE() to replace memmove() - -USAGE: - - There are four functions, one for each image file format: - - int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); - int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); - int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); - int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); - - There are also four equivalent functions that use an arbitrary write function. You are - expected to open/close your file-equivalent before and after calling these: - - int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); - int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); - int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); - int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); - - where the callback is: - void stbi_write_func(void *context, void *data, int size); - - You can define STBI_WRITE_NO_STDIO to disable the file variant of these - functions, so the library will not use stdio.h at all. However, this will - also disable HDR writing, because it requires stdio for formatted output. - - Each function returns 0 on failure and non-0 on success. - - The functions create an image file defined by the parameters. The image - is a rectangle of pixels stored from left-to-right, top-to-bottom. - Each pixel contains 'comp' channels of data stored interleaved with 8-bits - per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is - monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall. - The *data pointer points to the first byte of the top-left-most pixel. - For PNG, "stride_in_bytes" is the distance in bytes from the first byte of - a row of pixels to the first byte of the next row of pixels. - - PNG creates output files with the same number of components as the input. - The BMP format expands Y to RGB in the file format and does not - output alpha. - - PNG supports writing rectangles of data even when the bytes storing rows of - data are not consecutive in memory (e.g. sub-rectangles of a larger image), - by supplying the stride between the beginning of adjacent rows. The other - formats do not. (Thus you cannot write a native-format BMP through the BMP - writer, both because it is in BGR order and because it may have padding - at the end of the line.) - - HDR expects linear float data. Since the format is always 32-bit rgb(e) - data, alpha (if provided) is discarded, and for monochrome data it is - replicated across all three channels. - - TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed - data, set the global variable 'stbi_write_tga_with_rle' to 0. - -CREDITS: - - PNG/BMP/TGA - Sean Barrett - HDR - Baldur Karlsson - TGA monochrome: - Jean-Sebastien Guay - misc enhancements: - Tim Kelsey - TGA RLE - Alan Hickman - initial file IO callback implementation - Emmanuel Julien - bugfixes: - github:Chribba - Guillaume Chereau - github:jry2 - github:romigrou - Sergio Gonzalez - Jonas Karlsson - Filip Wasil - Thatcher Ulrich - github:poppolopoppo - Patrick Boettcher - -LICENSE - - See end of file for license information. - -*/ - -#ifndef INCLUDE_STB_IMAGE_WRITE_H -#define INCLUDE_STB_IMAGE_WRITE_H - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef STB_IMAGE_WRITE_STATIC -#define STBIWDEF static -#else -#define STBIWDEF extern -extern int stbi_write_tga_with_rle; -#endif - -#ifndef STBI_WRITE_NO_STDIO -STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); -STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); -STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); -STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); -#endif - -typedef void stbi_write_func(void *context, void *data, int size); - -STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); -STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); -STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); -STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); - -#ifdef __cplusplus -} -#endif - -#endif//INCLUDE_STB_IMAGE_WRITE_H - -#ifdef STB_IMAGE_WRITE_IMPLEMENTATION - -#ifdef _WIN32 - #ifndef _CRT_SECURE_NO_WARNINGS - #define _CRT_SECURE_NO_WARNINGS - #endif - #ifndef _CRT_NONSTDC_NO_DEPRECATE - #define _CRT_NONSTDC_NO_DEPRECATE - #endif -#endif - -#ifndef STBI_WRITE_NO_STDIO -#include -#endif // STBI_WRITE_NO_STDIO - -#include -#include -#include -#include - -#if defined(STBIW_MALLOC) && defined(STBIW_FREE) && (defined(STBIW_REALLOC) || defined(STBIW_REALLOC_SIZED)) -// ok -#elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC) && !defined(STBIW_REALLOC_SIZED) -// ok -#else -#error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC (or STBIW_REALLOC_SIZED)." -#endif - -#ifndef STBIW_MALLOC -#define STBIW_MALLOC(sz) malloc(sz) -#define STBIW_REALLOC(p,newsz) realloc(p,newsz) -#define STBIW_FREE(p) free(p) -#endif - -#ifndef STBIW_REALLOC_SIZED -#define STBIW_REALLOC_SIZED(p,oldsz,newsz) STBIW_REALLOC(p,newsz) -#endif - - -#ifndef STBIW_MEMMOVE -#define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz) -#endif - - -#ifndef STBIW_ASSERT -#include -#define STBIW_ASSERT(x) assert(x) -#endif - -#define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff) - -typedef struct -{ - stbi_write_func *func; - void *context; -} stbi__write_context; - -// initialize a callback-based context -static void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func *c, void *context) -{ - s->func = c; - s->context = context; -} - -#ifndef STBI_WRITE_NO_STDIO - -static void stbi__stdio_write(void *context, void *data, int size) -{ - fwrite(data,1,size,(FILE*) context); -} - -static int stbi__start_write_file(stbi__write_context *s, const char *filename) -{ - FILE *f = fopen(filename, "wb"); - stbi__start_write_callbacks(s, stbi__stdio_write, (void *) f); - return f != NULL; -} - -static void stbi__end_write_file(stbi__write_context *s) -{ - fclose((FILE *)s->context); -} - -#endif // !STBI_WRITE_NO_STDIO - -typedef unsigned int stbiw_uint32; -typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1]; - -#ifdef STB_IMAGE_WRITE_STATIC -static int stbi_write_tga_with_rle = 1; -#else -int stbi_write_tga_with_rle = 1; -#endif - -static void stbiw__writefv(stbi__write_context *s, const char *fmt, va_list v) -{ - while (*fmt) { - switch (*fmt++) { - case ' ': break; - case '1': { unsigned char x = STBIW_UCHAR(va_arg(v, int)); - s->func(s->context,&x,1); - break; } - case '2': { int x = va_arg(v,int); - unsigned char b[2]; - b[0] = STBIW_UCHAR(x); - b[1] = STBIW_UCHAR(x>>8); - s->func(s->context,b,2); - break; } - case '4': { stbiw_uint32 x = va_arg(v,int); - unsigned char b[4]; - b[0]=STBIW_UCHAR(x); - b[1]=STBIW_UCHAR(x>>8); - b[2]=STBIW_UCHAR(x>>16); - b[3]=STBIW_UCHAR(x>>24); - s->func(s->context,b,4); - break; } - default: - STBIW_ASSERT(0); - return; - } - } -} - -static void stbiw__writef(stbi__write_context *s, const char *fmt, ...) -{ - va_list v; - va_start(v, fmt); - stbiw__writefv(s, fmt, v); - va_end(v); -} - -static void stbiw__write3(stbi__write_context *s, unsigned char a, unsigned char b, unsigned char c) -{ - unsigned char arr[3]; - arr[0] = a, arr[1] = b, arr[2] = c; - s->func(s->context, arr, 3); -} - -static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, int write_alpha, int expand_mono, unsigned char *d) -{ - unsigned char bg[3] = { 255, 0, 255}, px[3]; - int k; - - if (write_alpha < 0) - s->func(s->context, &d[comp - 1], 1); - - switch (comp) { - case 2: // 2 pixels = mono + alpha, alpha is written separately, so same as 1-channel case - case 1: - if (expand_mono) - stbiw__write3(s, d[0], d[0], d[0]); // monochrome bmp - else - s->func(s->context, d, 1); // monochrome TGA - break; - case 4: - if (!write_alpha) { - // composite against pink background - for (k = 0; k < 3; ++k) - px[k] = bg[k] + ((d[k] - bg[k]) * d[3]) / 255; - stbiw__write3(s, px[1 - rgb_dir], px[1], px[1 + rgb_dir]); - break; - } - /* FALLTHROUGH */ - case 3: - stbiw__write3(s, d[1 - rgb_dir], d[1], d[1 + rgb_dir]); - break; - } - if (write_alpha > 0) - s->func(s->context, &d[comp - 1], 1); -} - -static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono) -{ - stbiw_uint32 zero = 0; - int i,j, j_end; - - if (y <= 0) - return; - - if (vdir < 0) - j_end = -1, j = y-1; - else - j_end = y, j = 0; - - for (; j != j_end; j += vdir) { - for (i=0; i < x; ++i) { - unsigned char *d = (unsigned char *) data + (j*x+i)*comp; - stbiw__write_pixel(s, rgb_dir, comp, write_alpha, expand_mono, d); - } - s->func(s->context, &zero, scanline_pad); - } -} - -static int stbiw__outfile(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, int expand_mono, void *data, int alpha, int pad, const char *fmt, ...) -{ - if (y < 0 || x < 0) { - return 0; - } else { - va_list v; - va_start(v, fmt); - stbiw__writefv(s, fmt, v); - va_end(v); - stbiw__write_pixels(s,rgb_dir,vdir,x,y,comp,data,alpha,pad, expand_mono); - return 1; - } -} - -static int stbi_write_bmp_core(stbi__write_context *s, int x, int y, int comp, const void *data) -{ - int pad = (-x*3) & 3; - return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *) data,0,pad, - "11 4 22 4" "4 44 22 444444", - 'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header - 40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header -} - -STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) -{ - stbi__write_context s; - stbi__start_write_callbacks(&s, func, context); - return stbi_write_bmp_core(&s, x, y, comp, data); -} - -#ifndef STBI_WRITE_NO_STDIO -STBIWDEF int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data) -{ - stbi__write_context s; - if (stbi__start_write_file(&s,filename)) { - int r = stbi_write_bmp_core(&s, x, y, comp, data); - stbi__end_write_file(&s); - return r; - } else - return 0; -} -#endif //!STBI_WRITE_NO_STDIO - -static int stbi_write_tga_core(stbi__write_context *s, int x, int y, int comp, void *data) -{ - int has_alpha = (comp == 2 || comp == 4); - int colorbytes = has_alpha ? comp-1 : comp; - int format = colorbytes < 2 ? 3 : 2; // 3 color channels (RGB/RGBA) = 2, 1 color channel (Y/YA) = 3 - - if (y < 0 || x < 0) - return 0; - - if (!stbi_write_tga_with_rle) { - return stbiw__outfile(s, -1, -1, x, y, comp, 0, (void *) data, has_alpha, 0, - "111 221 2222 11", 0, 0, format, 0, 0, 0, 0, 0, x, y, (colorbytes + has_alpha) * 8, has_alpha * 8); - } else { - int i,j,k; - - stbiw__writef(s, "111 221 2222 11", 0,0,format+8, 0,0,0, 0,0,x,y, (colorbytes + has_alpha) * 8, has_alpha * 8); - - for (j = y - 1; j >= 0; --j) { - unsigned char *row = (unsigned char *) data + j * x * comp; - int len; - - for (i = 0; i < x; i += len) { - unsigned char *begin = row + i * comp; - int diff = 1; - len = 1; - - if (i < x - 1) { - ++len; - diff = memcmp(begin, row + (i + 1) * comp, comp); - if (diff) { - const unsigned char *prev = begin; - for (k = i + 2; k < x && len < 128; ++k) { - if (memcmp(prev, row + k * comp, comp)) { - prev += comp; - ++len; - } else { - --len; - break; - } - } - } else { - for (k = i + 2; k < x && len < 128; ++k) { - if (!memcmp(begin, row + k * comp, comp)) { - ++len; - } else { - break; - } - } - } - } - - if (diff) { - unsigned char header = STBIW_UCHAR(len - 1); - s->func(s->context, &header, 1); - for (k = 0; k < len; ++k) { - stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin + k * comp); - } - } else { - unsigned char header = STBIW_UCHAR(len - 129); - s->func(s->context, &header, 1); - stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin); - } - } - } - } - return 1; -} - -int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) -{ - stbi__write_context s; - stbi__start_write_callbacks(&s, func, context); - return stbi_write_tga_core(&s, x, y, comp, (void *) data); -} - -#ifndef STBI_WRITE_NO_STDIO -int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data) -{ - stbi__write_context s; - if (stbi__start_write_file(&s,filename)) { - int r = stbi_write_tga_core(&s, x, y, comp, (void *) data); - stbi__end_write_file(&s); - return r; - } else - return 0; -} -#endif - -// ************************************************************************************************* -// Radiance RGBE HDR writer -// by Baldur Karlsson - -#define stbiw__max(a, b) ((a) > (b) ? (a) : (b)) - -void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear) -{ - int exponent; - float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2])); - - if (maxcomp < 1e-32f) { - rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; - } else { - float normalize = (float) frexp(maxcomp, &exponent) * 256.0f/maxcomp; - - rgbe[0] = (unsigned char)(linear[0] * normalize); - rgbe[1] = (unsigned char)(linear[1] * normalize); - rgbe[2] = (unsigned char)(linear[2] * normalize); - rgbe[3] = (unsigned char)(exponent + 128); - } -} - -void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char databyte) -{ - unsigned char lengthbyte = STBIW_UCHAR(length+128); - STBIW_ASSERT(length+128 <= 255); - s->func(s->context, &lengthbyte, 1); - s->func(s->context, &databyte, 1); -} - -void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *data) -{ - unsigned char lengthbyte = STBIW_UCHAR(length); - STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code - s->func(s->context, &lengthbyte, 1); - s->func(s->context, data, length); -} - -void stbiw__write_hdr_scanline(stbi__write_context *s, int width, int ncomp, unsigned char *scratch, float *scanline) -{ - unsigned char scanlineheader[4] = { 2, 2, 0, 0 }; - unsigned char rgbe[4]; - float linear[3]; - int x; - - scanlineheader[2] = (width&0xff00)>>8; - scanlineheader[3] = (width&0x00ff); - - /* skip RLE for images too small or large */ - if (width < 8 || width >= 32768) { - for (x=0; x < width; x++) { - switch (ncomp) { - case 4: /* fallthrough */ - case 3: linear[2] = scanline[x*ncomp + 2]; - linear[1] = scanline[x*ncomp + 1]; - linear[0] = scanline[x*ncomp + 0]; - break; - default: - linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; - break; - } - stbiw__linear_to_rgbe(rgbe, linear); - s->func(s->context, rgbe, 4); - } - } else { - int c,r; - /* encode into scratch buffer */ - for (x=0; x < width; x++) { - switch(ncomp) { - case 4: /* fallthrough */ - case 3: linear[2] = scanline[x*ncomp + 2]; - linear[1] = scanline[x*ncomp + 1]; - linear[0] = scanline[x*ncomp + 0]; - break; - default: - linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; - break; - } - stbiw__linear_to_rgbe(rgbe, linear); - scratch[x + width*0] = rgbe[0]; - scratch[x + width*1] = rgbe[1]; - scratch[x + width*2] = rgbe[2]; - scratch[x + width*3] = rgbe[3]; - } - - s->func(s->context, scanlineheader, 4); - - /* RLE each component separately */ - for (c=0; c < 4; c++) { - unsigned char *comp = &scratch[width*c]; - - x = 0; - while (x < width) { - // find first run - r = x; - while (r+2 < width) { - if (comp[r] == comp[r+1] && comp[r] == comp[r+2]) - break; - ++r; - } - if (r+2 >= width) - r = width; - // dump up to first run - while (x < r) { - int len = r-x; - if (len > 128) len = 128; - stbiw__write_dump_data(s, len, &comp[x]); - x += len; - } - // if there's a run, output it - if (r+2 < width) { // same test as what we break out of in search loop, so only true if we break'd - // find next byte after run - while (r < width && comp[r] == comp[x]) - ++r; - // output run up to r - while (x < r) { - int len = r-x; - if (len > 127) len = 127; - stbiw__write_run_data(s, len, comp[x]); - x += len; - } - } - } - } - } -} - -static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, float *data) -{ - if (y <= 0 || x <= 0 || data == NULL) - return 0; - else { - // Each component is stored separately. Allocate scratch space for full output scanline. - unsigned char *scratch = (unsigned char *) STBIW_MALLOC(x*4); - int i, len; - char buffer[128]; - char header[] = "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n"; - s->func(s->context, header, sizeof(header)-1); - - len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); - s->func(s->context, buffer, len); - - for(i=0; i < y; i++) - stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp*i*x); - STBIW_FREE(scratch); - return 1; - } -} - -int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const float *data) -{ - stbi__write_context s; - stbi__start_write_callbacks(&s, func, context); - return stbi_write_hdr_core(&s, x, y, comp, (float *) data); -} - -#ifndef STBI_WRITE_NO_STDIO -int stbi_write_hdr(char const *filename, int x, int y, int comp, const float *data) -{ - stbi__write_context s; - if (stbi__start_write_file(&s,filename)) { - int r = stbi_write_hdr_core(&s, x, y, comp, (float *) data); - stbi__end_write_file(&s); - return r; - } else - return 0; -} -#endif // STBI_WRITE_NO_STDIO - - -////////////////////////////////////////////////////////////////////////////// -// -// PNG writer -// - -// stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size() -#define stbiw__sbraw(a) ((int *) (a) - 2) -#define stbiw__sbm(a) stbiw__sbraw(a)[0] -#define stbiw__sbn(a) stbiw__sbraw(a)[1] - -#define stbiw__sbneedgrow(a,n) ((a)==0 || stbiw__sbn(a)+n >= stbiw__sbm(a)) -#define stbiw__sbmaybegrow(a,n) (stbiw__sbneedgrow(a,(n)) ? stbiw__sbgrow(a,n) : 0) -#define stbiw__sbgrow(a,n) stbiw__sbgrowf((void **) &(a), (n), sizeof(*(a))) - -#define stbiw__sbpush(a, v) (stbiw__sbmaybegrow(a,1), (a)[stbiw__sbn(a)++] = (v)) -#define stbiw__sbcount(a) ((a) ? stbiw__sbn(a) : 0) -#define stbiw__sbfree(a) ((a) ? STBIW_FREE(stbiw__sbraw(a)),0 : 0) - -static void *stbiw__sbgrowf(void **arr, int increment, int itemsize) -{ - int m = *arr ? 2*stbiw__sbm(*arr)+increment : increment+1; - void *p = STBIW_REALLOC_SIZED(*arr ? stbiw__sbraw(*arr) : 0, *arr ? (stbiw__sbm(*arr)*itemsize + sizeof(int)*2) : 0, itemsize * m + sizeof(int)*2); - STBIW_ASSERT(p); - if (p) { - if (!*arr) ((int *) p)[1] = 0; - *arr = (void *) ((int *) p + 2); - stbiw__sbm(*arr) = m; - } - return *arr; -} - -static unsigned char *stbiw__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount) -{ - while (*bitcount >= 8) { - stbiw__sbpush(data, STBIW_UCHAR(*bitbuffer)); - *bitbuffer >>= 8; - *bitcount -= 8; - } - return data; -} - -static int stbiw__zlib_bitrev(int code, int codebits) -{ - int res=0; - while (codebits--) { - res = (res << 1) | (code & 1); - code >>= 1; - } - return res; -} - -static unsigned int stbiw__zlib_countm(unsigned char *a, unsigned char *b, int limit) -{ - int i; - for (i=0; i < limit && i < 258; ++i) - if (a[i] != b[i]) break; - return i; -} - -static unsigned int stbiw__zhash(unsigned char *data) -{ - stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16); - hash ^= hash << 3; - hash += hash >> 5; - hash ^= hash << 4; - hash += hash >> 17; - hash ^= hash << 25; - hash += hash >> 6; - return hash; -} - -#define stbiw__zlib_flush() (out = stbiw__zlib_flushf(out, &bitbuf, &bitcount)) -#define stbiw__zlib_add(code,codebits) \ - (bitbuf |= (code) << bitcount, bitcount += (codebits), stbiw__zlib_flush()) -#define stbiw__zlib_huffa(b,c) stbiw__zlib_add(stbiw__zlib_bitrev(b,c),c) -// default huffman tables -#define stbiw__zlib_huff1(n) stbiw__zlib_huffa(0x30 + (n), 8) -#define stbiw__zlib_huff2(n) stbiw__zlib_huffa(0x190 + (n)-144, 9) -#define stbiw__zlib_huff3(n) stbiw__zlib_huffa(0 + (n)-256,7) -#define stbiw__zlib_huff4(n) stbiw__zlib_huffa(0xc0 + (n)-280,8) -#define stbiw__zlib_huff(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : (n) <= 255 ? stbiw__zlib_huff2(n) : (n) <= 279 ? stbiw__zlib_huff3(n) : stbiw__zlib_huff4(n)) -#define stbiw__zlib_huffb(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : stbiw__zlib_huff2(n)) - -#define stbiw__ZHASH 16384 - -unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) -{ - static unsigned short lengthc[] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258, 259 }; - static unsigned char lengtheb[]= { 0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; - static unsigned short distc[] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 32768 }; - static unsigned char disteb[] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 }; - unsigned int bitbuf=0; - int i,j, bitcount=0; - unsigned char *out = NULL; - unsigned char ***hash_table = (unsigned char***) STBIW_MALLOC(stbiw__ZHASH * sizeof(char**)); - if (quality < 5) quality = 5; - - stbiw__sbpush(out, 0x78); // DEFLATE 32K window - stbiw__sbpush(out, 0x5e); // FLEVEL = 1 - stbiw__zlib_add(1,1); // BFINAL = 1 - stbiw__zlib_add(1,2); // BTYPE = 1 -- fixed huffman - - for (i=0; i < stbiw__ZHASH; ++i) - hash_table[i] = NULL; - - i=0; - while (i < data_len-3) { - // hash next 3 bytes of data to be compressed - int h = stbiw__zhash(data+i)&(stbiw__ZHASH-1), best=3; - unsigned char *bestloc = 0; - unsigned char **hlist = hash_table[h]; - int n = stbiw__sbcount(hlist); - for (j=0; j < n; ++j) { - if (hlist[j]-data > i-32768) { // if entry lies within window - int d = stbiw__zlib_countm(hlist[j], data+i, data_len-i); - if (d >= best) best=d,bestloc=hlist[j]; - } - } - // when hash table entry is too long, delete half the entries - if (hash_table[h] && stbiw__sbn(hash_table[h]) == 2*quality) { - STBIW_MEMMOVE(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality); - stbiw__sbn(hash_table[h]) = quality; - } - stbiw__sbpush(hash_table[h],data+i); - - if (bestloc) { - // "lazy matching" - check match at *next* byte, and if it's better, do cur byte as literal - h = stbiw__zhash(data+i+1)&(stbiw__ZHASH-1); - hlist = hash_table[h]; - n = stbiw__sbcount(hlist); - for (j=0; j < n; ++j) { - if (hlist[j]-data > i-32767) { - int e = stbiw__zlib_countm(hlist[j], data+i+1, data_len-i-1); - if (e > best) { // if next match is better, bail on current match - bestloc = NULL; - break; - } - } - } - } - - if (bestloc) { - int d = (int) (data+i - bestloc); // distance back - STBIW_ASSERT(d <= 32767 && best <= 258); - for (j=0; best > lengthc[j+1]-1; ++j); - stbiw__zlib_huff(j+257); - if (lengtheb[j]) stbiw__zlib_add(best - lengthc[j], lengtheb[j]); - for (j=0; d > distc[j+1]-1; ++j); - stbiw__zlib_add(stbiw__zlib_bitrev(j,5),5); - if (disteb[j]) stbiw__zlib_add(d - distc[j], disteb[j]); - i += best; - } else { - stbiw__zlib_huffb(data[i]); - ++i; - } - } - // write out final bytes - for (;i < data_len; ++i) - stbiw__zlib_huffb(data[i]); - stbiw__zlib_huff(256); // end of block - // pad with 0 bits to byte boundary - while (bitcount) - stbiw__zlib_add(0,1); - - for (i=0; i < stbiw__ZHASH; ++i) - (void) stbiw__sbfree(hash_table[i]); - STBIW_FREE(hash_table); - - { - // compute adler32 on input - unsigned int s1=1, s2=0; - int blocklen = (int) (data_len % 5552); - j=0; - while (j < data_len) { - for (i=0; i < blocklen; ++i) s1 += data[j+i], s2 += s1; - s1 %= 65521, s2 %= 65521; - j += blocklen; - blocklen = 5552; - } - stbiw__sbpush(out, STBIW_UCHAR(s2 >> 8)); - stbiw__sbpush(out, STBIW_UCHAR(s2)); - stbiw__sbpush(out, STBIW_UCHAR(s1 >> 8)); - stbiw__sbpush(out, STBIW_UCHAR(s1)); - } - *out_len = stbiw__sbn(out); - // make returned pointer freeable - STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len); - return (unsigned char *) stbiw__sbraw(out); -} - -static unsigned int stbiw__crc32(unsigned char *buffer, int len) -{ - static unsigned int crc_table[256] = - { - 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, - 0x0eDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, - 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, - 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, - 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, - 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, - 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, - 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, - 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, - 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, - 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, - 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, - 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, - 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, - 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, - 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, - 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, - 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, - 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, - 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, - 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, - 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, - 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, - 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, - 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, - 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, - 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, - 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, - 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, - 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, - 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, - 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D - }; - - unsigned int crc = ~0u; - int i; - for (i=0; i < len; ++i) - crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)]; - return ~crc; -} - -#define stbiw__wpng4(o,a,b,c,d) ((o)[0]=STBIW_UCHAR(a),(o)[1]=STBIW_UCHAR(b),(o)[2]=STBIW_UCHAR(c),(o)[3]=STBIW_UCHAR(d),(o)+=4) -#define stbiw__wp32(data,v) stbiw__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v)); -#define stbiw__wptag(data,s) stbiw__wpng4(data, s[0],s[1],s[2],s[3]) - -static void stbiw__wpcrc(unsigned char **data, int len) -{ - unsigned int crc = stbiw__crc32(*data - len - 4, len+4); - stbiw__wp32(*data, crc); -} - -static unsigned char stbiw__paeth(int a, int b, int c) -{ - int p = a + b - c, pa = abs(p-a), pb = abs(p-b), pc = abs(p-c); - if (pa <= pb && pa <= pc) return STBIW_UCHAR(a); - if (pb <= pc) return STBIW_UCHAR(b); - return STBIW_UCHAR(c); -} - -// @OPTIMIZE: provide an option that always forces left-predict or paeth predict -unsigned char *stbi_write_png_to_mem(unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len) -{ - int ctype[5] = { -1, 0, 4, 2, 6 }; - unsigned char sig[8] = { 137,80,78,71,13,10,26,10 }; - unsigned char *out,*o, *filt, *zlib; - signed char *line_buffer; - int i,j,k,p,zlen; - - if (stride_bytes == 0) - stride_bytes = x * n; - - filt = (unsigned char *) STBIW_MALLOC((x*n+1) * y); if (!filt) return 0; - line_buffer = (signed char *) STBIW_MALLOC(x * n); if (!line_buffer) { STBIW_FREE(filt); return 0; } - for (j=0; j < y; ++j) { - static int mapping[] = { 0,1,2,3,4 }; - static int firstmap[] = { 0,1,0,5,6 }; - int *mymap = (j != 0) ? mapping : firstmap; - int best = 0, bestval = 0x7fffffff; - for (p=0; p < 2; ++p) { - for (k= p?best:0; k < 5; ++k) { // @TODO: clarity: rewrite this to go 0..5, and 'continue' the unwanted ones during 2nd pass - int type = mymap[k],est=0; - unsigned char *z = pixels + stride_bytes*j; - for (i=0; i < n; ++i) - switch (type) { - case 0: line_buffer[i] = z[i]; break; - case 1: line_buffer[i] = z[i]; break; - case 2: line_buffer[i] = z[i] - z[i-stride_bytes]; break; - case 3: line_buffer[i] = z[i] - (z[i-stride_bytes]>>1); break; - case 4: line_buffer[i] = (signed char) (z[i] - stbiw__paeth(0,z[i-stride_bytes],0)); break; - case 5: line_buffer[i] = z[i]; break; - case 6: line_buffer[i] = z[i]; break; - } - for (i=n; i < x*n; ++i) { - switch (type) { - case 0: line_buffer[i] = z[i]; break; - case 1: line_buffer[i] = z[i] - z[i-n]; break; - case 2: line_buffer[i] = z[i] - z[i-stride_bytes]; break; - case 3: line_buffer[i] = z[i] - ((z[i-n] + z[i-stride_bytes])>>1); break; - case 4: line_buffer[i] = z[i] - stbiw__paeth(z[i-n], z[i-stride_bytes], z[i-stride_bytes-n]); break; - case 5: line_buffer[i] = z[i] - (z[i-n]>>1); break; - case 6: line_buffer[i] = z[i] - stbiw__paeth(z[i-n], 0,0); break; - } - } - if (p) break; - for (i=0; i < x*n; ++i) - est += abs((signed char) line_buffer[i]); - if (est < bestval) { bestval = est; best = k; } - } - } - // when we get here, best contains the filter type, and line_buffer contains the data - filt[j*(x*n+1)] = (unsigned char) best; - STBIW_MEMMOVE(filt+j*(x*n+1)+1, line_buffer, x*n); - } - STBIW_FREE(line_buffer); - zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, 8); // increase 8 to get smaller but use more memory - STBIW_FREE(filt); - if (!zlib) return 0; - - // each tag requires 12 bytes of overhead - out = (unsigned char *) STBIW_MALLOC(8 + 12+13 + 12+zlen + 12); - if (!out) return 0; - *out_len = 8 + 12+13 + 12+zlen + 12; - - o=out; - STBIW_MEMMOVE(o,sig,8); o+= 8; - stbiw__wp32(o, 13); // header length - stbiw__wptag(o, "IHDR"); - stbiw__wp32(o, x); - stbiw__wp32(o, y); - *o++ = 8; - *o++ = STBIW_UCHAR(ctype[n]); - *o++ = 0; - *o++ = 0; - *o++ = 0; - stbiw__wpcrc(&o,13); - - stbiw__wp32(o, zlen); - stbiw__wptag(o, "IDAT"); - STBIW_MEMMOVE(o, zlib, zlen); - o += zlen; - STBIW_FREE(zlib); - stbiw__wpcrc(&o, zlen); - - stbiw__wp32(o,0); - stbiw__wptag(o, "IEND"); - stbiw__wpcrc(&o,0); - - STBIW_ASSERT(o == out + *out_len); - - return out; -} - -#ifndef STBI_WRITE_NO_STDIO -STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes) -{ - FILE *f; - int len; - unsigned char *png = stbi_write_png_to_mem((unsigned char *) data, stride_bytes, x, y, comp, &len); - if (png == NULL) return 0; - f = fopen(filename, "wb"); - if (!f) { STBIW_FREE(png); return 0; } - fwrite(png, 1, len, f); - fclose(f); - STBIW_FREE(png); - return 1; -} -#endif - -STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int stride_bytes) -{ - int len; - unsigned char *png = stbi_write_png_to_mem((unsigned char *) data, stride_bytes, x, y, comp, &len); - if (png == NULL) return 0; - func(context, png, len); - STBIW_FREE(png); - return 1; -} - -#endif // STB_IMAGE_WRITE_IMPLEMENTATION - -/* Revision history - 1.04 (2017-03-03) - monochrome BMP expansion - 1.03 ??? - 1.02 (2016-04-02) - avoid allocating large structures on the stack - 1.01 (2016-01-16) - STBIW_REALLOC_SIZED: support allocators with no realloc support - avoid race-condition in crc initialization - minor compile issues - 1.00 (2015-09-14) - installable file IO function - 0.99 (2015-09-13) - warning fixes; TGA rle support - 0.98 (2015-04-08) - added STBIW_MALLOC, STBIW_ASSERT etc - 0.97 (2015-01-18) - fixed HDR asserts, rewrote HDR rle logic - 0.96 (2015-01-17) - add HDR output - fix monochrome BMP - 0.95 (2014-08-17) - add monochrome TGA output - 0.94 (2014-05-31) - rename private functions to avoid conflicts with stb_image.h - 0.93 (2014-05-27) - warning fixes - 0.92 (2010-08-01) - casts to unsigned char to fix warnings - 0.91 (2010-07-17) - first public release - 0.90 first internal release -*/ - -/* ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright (c) 2017 Sean Barrett -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain (www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- -*/ diff --git a/lib/SOIL2/stbi_DDS.h b/lib/SOIL2/stbi_DDS.h deleted file mode 100644 index fbf8193bf..000000000 --- a/lib/SOIL2/stbi_DDS.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - adding DDS loading support to stbi -*/ - -#ifndef HEADER_STB_IMAGE_DDS_AUGMENTATION -#define HEADER_STB_IMAGE_DDS_AUGMENTATION - -/* is it a DDS file? */ -extern int stbi__dds_test_memory (stbi_uc const *buffer, int len); -extern int stbi__dds_test_callbacks (stbi_io_callbacks const *clbk, void *user); - -extern void *stbi__dds_load_from_path (const char *filename, int *x, int *y, int *comp, int req_comp); -extern void *stbi__dds_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); -extern void *stbi__dds_load_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp); - -#ifndef STBI_NO_STDIO -extern int stbi__dds_test_filename (char const *filename); -extern int stbi__dds_test_file (FILE *f); -extern void *stbi__dds_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); -#endif - -extern int stbi__dds_info_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int *iscompressed); -extern int stbi__dds_info_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int *iscompressed); - - -#ifndef STBI_NO_STDIO -extern int stbi__dds_info_from_path (char const *filename, int *x, int *y, int *comp, int *iscompressed); -extern int stbi__dds_info_from_file (FILE *f, int *x, int *y, int *comp, int *iscompressed); -#endif - -/* -// -//// end header file /////////////////////////////////////////////////////*/ -#endif /* HEADER_STB_IMAGE_DDS_AUGMENTATION */ diff --git a/lib/SOIL2/stbi_DDS_c.h b/lib/SOIL2/stbi_DDS_c.h deleted file mode 100644 index 392065b12..000000000 --- a/lib/SOIL2/stbi_DDS_c.h +++ /dev/null @@ -1,589 +0,0 @@ - -/// DDS file support, does decoding, _not_ direct uploading -/// (use SOIL for that ;-) - -#include "image_DXT.h" - -static int stbi__dds_test(stbi__context *s) -{ - // check the magic number - if (stbi__get8(s) != 'D') { - stbi__rewind(s); - return 0; - } - - if (stbi__get8(s) != 'D') { - stbi__rewind(s); - return 0; - } - - if (stbi__get8(s) != 'S') { - stbi__rewind(s); - return 0; - } - - if (stbi__get8(s) != ' ') { - stbi__rewind(s); - return 0; - } - - // check header size - if (stbi__get32le(s) != 124) { - stbi__rewind(s); - return 0; - } - - // Also rewind because the loader needs to read the header - stbi__rewind(s); - - return 1; -} -#ifndef STBI_NO_STDIO - -int stbi__dds_test_filename (char const *filename) -{ - int r; - FILE *f = fopen(filename, "rb"); - if (!f) return 0; - r = stbi__dds_test_file(f); - fclose(f); - return r; -} - -int stbi__dds_test_file (FILE *f) -{ - stbi__context s; - int r,n = ftell(f); - stbi__start_file(&s,f); - r = stbi__dds_test(&s); - fseek(f,n,SEEK_SET); - return r; -} -#endif - -int stbi__dds_test_memory (stbi_uc const *buffer, int len) -{ - stbi__context s; - stbi__start_mem(&s,buffer, len); - return stbi__dds_test(&s); -} - -int stbi__dds_test_callbacks (stbi_io_callbacks const *clbk, void *user) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi__dds_test(&s); -} - -// helper functions -int stbi_convert_bit_range( int c, int from_bits, int to_bits ) -{ - int b = (1 << (from_bits - 1)) + c * ((1 << to_bits) - 1); - return (b + (b >> from_bits)) >> from_bits; -} -void stbi_rgb_888_from_565( unsigned int c, int *r, int *g, int *b ) -{ - *r = stbi_convert_bit_range( (c >> 11) & 31, 5, 8 ); - *g = stbi_convert_bit_range( (c >> 05) & 63, 6, 8 ); - *b = stbi_convert_bit_range( (c >> 00) & 31, 5, 8 ); -} -void stbi_decode_DXT1_block( - unsigned char uncompressed[16*4], - unsigned char compressed[8] ) -{ - int next_bit = 4*8; - int i, r, g, b; - int c0, c1; - unsigned char decode_colors[4*4]; - // find the 2 primary colors - c0 = compressed[0] + (compressed[1] << 8); - c1 = compressed[2] + (compressed[3] << 8); - stbi_rgb_888_from_565( c0, &r, &g, &b ); - decode_colors[0] = r; - decode_colors[1] = g; - decode_colors[2] = b; - decode_colors[3] = 255; - stbi_rgb_888_from_565( c1, &r, &g, &b ); - decode_colors[4] = r; - decode_colors[5] = g; - decode_colors[6] = b; - decode_colors[7] = 255; - if( c0 > c1 ) - { - // no alpha, 2 interpolated colors - decode_colors[8] = (2*decode_colors[0] + decode_colors[4]) / 3; - decode_colors[9] = (2*decode_colors[1] + decode_colors[5]) / 3; - decode_colors[10] = (2*decode_colors[2] + decode_colors[6]) / 3; - decode_colors[11] = 255; - decode_colors[12] = (decode_colors[0] + 2*decode_colors[4]) / 3; - decode_colors[13] = (decode_colors[1] + 2*decode_colors[5]) / 3; - decode_colors[14] = (decode_colors[2] + 2*decode_colors[6]) / 3; - decode_colors[15] = 255; - } else - { - // 1 interpolated color, alpha - decode_colors[8] = (decode_colors[0] + decode_colors[4]) / 2; - decode_colors[9] = (decode_colors[1] + decode_colors[5]) / 2; - decode_colors[10] = (decode_colors[2] + decode_colors[6]) / 2; - decode_colors[11] = 255; - decode_colors[12] = 0; - decode_colors[13] = 0; - decode_colors[14] = 0; - decode_colors[15] = 0; - } - // decode the block - for( i = 0; i < 16*4; i += 4 ) - { - int idx = ((compressed[next_bit>>3] >> (next_bit & 7)) & 3) * 4; - next_bit += 2; - uncompressed[i+0] = decode_colors[idx+0]; - uncompressed[i+1] = decode_colors[idx+1]; - uncompressed[i+2] = decode_colors[idx+2]; - uncompressed[i+3] = decode_colors[idx+3]; - } - // done -} -void stbi_decode_DXT23_alpha_block( - unsigned char uncompressed[16*4], - unsigned char compressed[8] ) -{ - int i, next_bit = 0; - // each alpha value gets 4 bits - for( i = 3; i < 16*4; i += 4 ) - { - uncompressed[i] = stbi_convert_bit_range( - (compressed[next_bit>>3] >> (next_bit&7)) & 15, - 4, 8 ); - next_bit += 4; - } -} -void stbi_decode_DXT45_alpha_block( - unsigned char uncompressed[16*4], - unsigned char compressed[8] ) -{ - int i, next_bit = 8*2; - unsigned char decode_alpha[8]; - // each alpha value gets 3 bits, and the 1st 2 bytes are the range - decode_alpha[0] = compressed[0]; - decode_alpha[1] = compressed[1]; - if( decode_alpha[0] > decode_alpha[1] ) - { - // 6 step intermediate - decode_alpha[2] = (6*decode_alpha[0] + 1*decode_alpha[1]) / 7; - decode_alpha[3] = (5*decode_alpha[0] + 2*decode_alpha[1]) / 7; - decode_alpha[4] = (4*decode_alpha[0] + 3*decode_alpha[1]) / 7; - decode_alpha[5] = (3*decode_alpha[0] + 4*decode_alpha[1]) / 7; - decode_alpha[6] = (2*decode_alpha[0] + 5*decode_alpha[1]) / 7; - decode_alpha[7] = (1*decode_alpha[0] + 6*decode_alpha[1]) / 7; - } else - { - // 4 step intermediate, pluss full and none - decode_alpha[2] = (4*decode_alpha[0] + 1*decode_alpha[1]) / 5; - decode_alpha[3] = (3*decode_alpha[0] + 2*decode_alpha[1]) / 5; - decode_alpha[4] = (2*decode_alpha[0] + 3*decode_alpha[1]) / 5; - decode_alpha[5] = (1*decode_alpha[0] + 4*decode_alpha[1]) / 5; - decode_alpha[6] = 0; - decode_alpha[7] = 255; - } - for( i = 3; i < 16*4; i += 4 ) - { - int idx = 0, bit; - bit = (compressed[next_bit>>3] >> (next_bit&7)) & 1; - idx += bit << 0; - ++next_bit; - bit = (compressed[next_bit>>3] >> (next_bit&7)) & 1; - idx += bit << 1; - ++next_bit; - bit = (compressed[next_bit>>3] >> (next_bit&7)) & 1; - idx += bit << 2; - ++next_bit; - uncompressed[i] = decode_alpha[idx & 7]; - } - // done -} -void stbi_decode_DXT_color_block( - unsigned char uncompressed[16*4], - unsigned char compressed[8] ) -{ - int next_bit = 4*8; - int i, r, g, b; - int c0, c1; - unsigned char decode_colors[4*3]; - // find the 2 primary colors - c0 = compressed[0] + (compressed[1] << 8); - c1 = compressed[2] + (compressed[3] << 8); - stbi_rgb_888_from_565( c0, &r, &g, &b ); - decode_colors[0] = r; - decode_colors[1] = g; - decode_colors[2] = b; - stbi_rgb_888_from_565( c1, &r, &g, &b ); - decode_colors[3] = r; - decode_colors[4] = g; - decode_colors[5] = b; - // Like DXT1, but no choicees: - // no alpha, 2 interpolated colors - decode_colors[6] = (2*decode_colors[0] + decode_colors[3]) / 3; - decode_colors[7] = (2*decode_colors[1] + decode_colors[4]) / 3; - decode_colors[8] = (2*decode_colors[2] + decode_colors[5]) / 3; - decode_colors[9] = (decode_colors[0] + 2*decode_colors[3]) / 3; - decode_colors[10] = (decode_colors[1] + 2*decode_colors[4]) / 3; - decode_colors[11] = (decode_colors[2] + 2*decode_colors[5]) / 3; - // decode the block - for( i = 0; i < 16*4; i += 4 ) - { - int idx = ((compressed[next_bit>>3] >> (next_bit & 7)) & 3) * 3; - next_bit += 2; - uncompressed[i+0] = decode_colors[idx+0]; - uncompressed[i+1] = decode_colors[idx+1]; - uncompressed[i+2] = decode_colors[idx+2]; - } - // done -} - -static int stbi__dds_info( stbi__context *s, int *x, int *y, int *comp, int *iscompressed ) { - int flags,is_compressed,has_alpha; - DDS_header header={0}; - - if( sizeof( DDS_header ) != 128 ) - { - return 0; - } - - stbi__getn( s, (stbi_uc*)(&header), 128 ); - - if( header.dwMagic != (('D' << 0) | ('D' << 8) | ('S' << 16) | (' ' << 24)) ) { - stbi__rewind( s ); - return 0; - } - if( header.dwSize != 124 ) { - stbi__rewind( s ); - return 0; - } - flags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT; - if( (header.dwFlags & flags) != flags ) { - stbi__rewind( s ); - return 0; - } - if( header.sPixelFormat.dwSize != 32 ) { - stbi__rewind( s ); - return 0; - } - flags = DDPF_FOURCC | DDPF_RGB; - if( (header.sPixelFormat.dwFlags & flags) == 0 ) { - stbi__rewind( s ); - return 0; - } - if( (header.sCaps.dwCaps1 & DDSCAPS_TEXTURE) == 0 ) { - stbi__rewind( s ); - return 0; - } - - is_compressed = (header.sPixelFormat.dwFlags & DDPF_FOURCC) / DDPF_FOURCC; - has_alpha = (header.sPixelFormat.dwFlags & DDPF_ALPHAPIXELS) / DDPF_ALPHAPIXELS; - - *x = header.dwWidth; - *y = header.dwHeight; - - if ( !is_compressed ) { - *comp = 3; - - if ( has_alpha ) - *comp = 4; - } - else - *comp = 4; - - if ( iscompressed ) - *iscompressed = is_compressed; - - return 1; -} - -int stbi__dds_info_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int *iscompressed) -{ - stbi__context s; - stbi__start_mem(&s,buffer, len); - return stbi__dds_info( &s, x, y, comp, iscompressed ); -} - -int stbi__dds_info_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int *iscompressed) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi__dds_info( &s, x, y, comp, iscompressed ); -} - -#ifndef STBI_NO_STDIO -int stbi__dds_info_from_path(char const *filename, int *x, int *y, int *comp, int *iscompressed) -{ - int res; - FILE *f = fopen(filename, "rb"); - if (!f) return 0; - res = stbi__dds_info_from_file( f, x, y, comp, iscompressed ); - fclose(f); - return res; -} - -int stbi__dds_info_from_file(FILE *f, int *x, int *y, int *comp, int *iscompressed) -{ - stbi__context s; - int res; - long n = ftell(f); - stbi__start_file(&s, f); - res = stbi__dds_info(&s, x, y, comp, iscompressed); - fseek(f, n, SEEK_SET); - return res; -} -#endif - -static void * stbi__dds_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - // all variables go up front - stbi_uc *dds_data = NULL; - stbi_uc block[16*4]; - stbi_uc compressed[8]; - int flags, DXT_family; - int has_alpha, has_mipmap; - int is_compressed, cubemap_faces; - int block_pitch, num_blocks; - DDS_header header={0}; - int i, sz, cf; - // load the header - if( sizeof( DDS_header ) != 128 ) - { - return NULL; - } - stbi__getn( s, (stbi_uc*)(&header), 128 ); - // and do some checking - if( header.dwMagic != (('D' << 0) | ('D' << 8) | ('S' << 16) | (' ' << 24)) ) return NULL; - if( header.dwSize != 124 ) return NULL; - flags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT; - if( (header.dwFlags & flags) != flags ) return NULL; - /* According to the MSDN spec, the dwFlags should contain - DDSD_LINEARSIZE if it's compressed, or DDSD_PITCH if - uncompressed. Some DDS writers do not conform to the - spec, so I need to make my reader more tolerant */ - if( header.sPixelFormat.dwSize != 32 ) return NULL; - flags = DDPF_FOURCC | DDPF_RGB; - if( (header.sPixelFormat.dwFlags & flags) == 0 ) return NULL; - if( (header.sCaps.dwCaps1 & DDSCAPS_TEXTURE) == 0 ) return NULL; - // get the image data - s->img_x = header.dwWidth; - s->img_y = header.dwHeight; - s->img_n = 4; - is_compressed = (header.sPixelFormat.dwFlags & DDPF_FOURCC) / DDPF_FOURCC; - has_alpha = (header.sPixelFormat.dwFlags & DDPF_ALPHAPIXELS) / DDPF_ALPHAPIXELS; - has_mipmap = (header.sCaps.dwCaps1 & DDSCAPS_MIPMAP) && (header.dwMipMapCount > 1); - cubemap_faces = (header.sCaps.dwCaps2 & DDSCAPS2_CUBEMAP) / DDSCAPS2_CUBEMAP; - /* I need cubemaps to have square faces */ - cubemap_faces &= (s->img_x == s->img_y); - cubemap_faces *= 5; - cubemap_faces += 1; - block_pitch = (s->img_x+3) >> 2; - num_blocks = block_pitch * ((s->img_y+3) >> 2); - /* let the user know what's going on */ - *x = s->img_x; - *y = s->img_y; - *comp = s->img_n; - /* is this uncompressed? */ - if( is_compressed ) - { - /* compressed */ - // note: header.sPixelFormat.dwFourCC is something like (('D'<<0)|('X'<<8)|('T'<<16)|('1'<<24)) - DXT_family = 1 + (header.sPixelFormat.dwFourCC >> 24) - '1'; - if( (DXT_family < 1) || (DXT_family > 5) ) return NULL; - /* check the expected size...oops, nevermind... - those non-compliant writers leave - dwPitchOrLinearSize == 0 */ - // passed all the tests, get the RAM for decoding - sz = (s->img_x)*(s->img_y)*4*cubemap_faces; - dds_data = (unsigned char*)malloc( sz ); - /* do this once for each face */ - for( cf = 0; cf < cubemap_faces; ++ cf ) - { - // now read and decode all the blocks - for( i = 0; i < num_blocks; ++i ) - { - // where are we? - int bx, by, bw=4, bh=4; - int ref_x = 4 * (i % block_pitch); - int ref_y = 4 * (i / block_pitch); - // get the next block's worth of compressed data, and decompress it - if( DXT_family == 1 ) - { - // DXT1 - stbi__getn( s, compressed, 8 ); - stbi_decode_DXT1_block( block, compressed ); - } else if( DXT_family < 4 ) - { - // DXT2/3 - stbi__getn( s, compressed, 8 ); - stbi_decode_DXT23_alpha_block ( block, compressed ); - stbi__getn( s, compressed, 8 ); - stbi_decode_DXT_color_block ( block, compressed ); - } else - { - // DXT4/5 - stbi__getn( s, compressed, 8 ); - stbi_decode_DXT45_alpha_block ( block, compressed ); - stbi__getn( s, compressed, 8 ); - stbi_decode_DXT_color_block ( block, compressed ); - } - // is this a partial block? - if( ref_x + 4 > (int)s->img_x ) - { - bw = s->img_x - ref_x; - } - if( ref_y + 4 > (int)s->img_y ) - { - bh = s->img_y - ref_y; - } - // now drop our decompressed data into the buffer - for( by = 0; by < bh; ++by ) - { - int idx = 4*((ref_y+by+cf*s->img_x)*s->img_x + ref_x); - for( bx = 0; bx < bw*4; ++bx ) - { - - dds_data[idx+bx] = block[by*16+bx]; - } - } - } - /* done reading and decoding the main image... - stbi__skip MIPmaps if present */ - if( has_mipmap ) - { - int block_size = 16; - if( DXT_family == 1 ) - { - block_size = 8; - } - for( i = 1; i < (int)header.dwMipMapCount; ++i ) - { - int mx = s->img_x >> (i + 2); - int my = s->img_y >> (i + 2); - if( mx < 1 ) - { - mx = 1; - } - if( my < 1 ) - { - my = 1; - } - stbi__skip( s, mx*my*block_size ); - } - } - }/* per cubemap face */ - } else - { - /* uncompressed */ - DXT_family = 0; - s->img_n = 3; - if( has_alpha ) - { - s->img_n = 4; - } - *comp = s->img_n; - sz = s->img_x*s->img_y*s->img_n*cubemap_faces; - dds_data = (unsigned char*)malloc( sz ); - /* do this once for each face */ - for( cf = 0; cf < cubemap_faces; ++ cf ) - { - /* read the main image for this face */ - stbi__getn( s, &dds_data[cf*s->img_x*s->img_y*s->img_n], s->img_x*s->img_y*s->img_n ); - /* done reading and decoding the main image... - stbi__skip MIPmaps if present */ - if( has_mipmap ) - { - for( i = 1; i < (int)header.dwMipMapCount; ++i ) - { - int mx = s->img_x >> i; - int my = s->img_y >> i; - if( mx < 1 ) - { - mx = 1; - } - if( my < 1 ) - { - my = 1; - } - stbi__skip( s, mx*my*s->img_n ); - } - } - } - /* data was BGR, I need it RGB */ - for( i = 0; i < sz; i += s->img_n ) - { - unsigned char temp = dds_data[i]; - dds_data[i] = dds_data[i+2]; - dds_data[i+2] = temp; - } - } - /* finished decompressing into RGBA, - adjust the y size if we have a cubemap - note: sz is already up to date */ - s->img_y *= cubemap_faces; - *y = s->img_y; - // did the user want something else, or - // see if all the alpha values are 255 (i.e. no transparency) - has_alpha = 0; - if( s->img_n == 4) - { - for( i = 3; (i < sz) && (has_alpha == 0); i += 4 ) - { - has_alpha |= (dds_data[i] < 255); - } - } - if( (req_comp <= 4) && (req_comp >= 1) ) - { - // user has some requirements, meet them - if( req_comp != s->img_n ) - { - dds_data = stbi__convert_format( dds_data, s->img_n, req_comp, s->img_x, s->img_y ); - *comp = req_comp; - } - } else - { - // user had no requirements, only drop to RGB is no alpha - if( (has_alpha == 0) && (s->img_n == 4) ) - { - dds_data = stbi__convert_format( dds_data, 4, 3, s->img_x, s->img_y ); - *comp = 3; - } - } - // OK, done - return dds_data; -} - -#ifndef STBI_NO_STDIO -void *stbi__dds_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_file(&s,f); - return stbi__dds_load(&s,x,y,comp,req_comp); -} - -void *stbi__dds_load_from_path (const char *filename, int *x, int *y, int *comp, int req_comp) -{ - void *data; - FILE *f = fopen(filename, "rb"); - if (!f) return NULL; - data = stbi__dds_load_from_file(f,x,y,comp,req_comp); - fclose(f); - return data; -} -#endif - -void *stbi__dds_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_mem(&s,buffer, len); - return stbi__dds_load(&s,x,y,comp,req_comp); -} - -void *stbi__dds_load_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi__dds_load(&s,x,y,comp,req_comp); -} diff --git a/lib/SOIL2/stbi_ext.h b/lib/SOIL2/stbi_ext.h deleted file mode 100644 index de0ecc879..000000000 --- a/lib/SOIL2/stbi_ext.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef HEADER_STB_IMAGE_EXT -#define HEADER_STB_IMAGE_EXT - -enum { - STBI_unknown= 0, - STBI_jpeg = 1, - STBI_png = 2, - STBI_bmp = 3, - STBI_gif = 4, - STBI_tga = 5, - STBI_psd = 6, - STBI_pic = 7, - STBI_pnm = 8, - STBI_dds = 9, - STBI_pvr = 10, - STBI_pkm = 11, - STBI_hdr = 12 -}; - -extern int stbi_test_from_memory (stbi_uc const *buffer, int len); -extern int stbi_test_from_callbacks (stbi_io_callbacks const *clbk, void *user); - -#ifndef STBI_NO_STDIO -extern int stbi_test (char const *filename); -extern int stbi_test_from_file (FILE *f); -#endif - -#endif /* HEADER_STB_IMAGE_EXT */ diff --git a/lib/SOIL2/stbi_ext_c.h b/lib/SOIL2/stbi_ext_c.h deleted file mode 100644 index 224f436bc..000000000 --- a/lib/SOIL2/stbi_ext_c.h +++ /dev/null @@ -1,74 +0,0 @@ - -static int stbi_test_main(stbi__context *s) -{ - #ifndef STBI_NO_JPEG - if (stbi__jpeg_test(s)) return STBI_jpeg; - #endif - #ifndef STBI_NO_PNG - if (stbi__png_test(s)) return STBI_png; - #endif - #ifndef STBI_NO_BMP - if (stbi__bmp_test(s)) return STBI_bmp; - #endif - #ifndef STBI_NO_GIF - if (stbi__gif_test(s)) return STBI_gif; - #endif - #ifndef STBI_NO_PSD - if (stbi__psd_test(s)) return STBI_psd; - #endif - #ifndef STBI_NO_PIC - if (stbi__pic_test(s)) return STBI_pic; - #endif - #ifndef STBI_NO_PNM - if (stbi__pnm_test(s)) return STBI_pnm; - #endif - #ifndef STBI_NO_DDS - if (stbi__dds_test(s)) return STBI_dds; - #endif - #ifndef STBI_NO_PVR - if (stbi__pvr_test(s)) return STBI_pvr; - #endif - #ifndef STBI_NO_PKM - if (stbi__pkm_test(s)) return STBI_pkm; - #endif - #ifndef STBI_NO_HDR - if (stbi__hdr_test(s)) return STBI_hdr; - #endif - #ifndef STBI_NO_TGA - if (stbi__tga_test(s)) return STBI_tga; - #endif - return STBI_unknown; -} - -#ifndef STBI_NO_STDIO -int stbi_test_from_file(FILE *f) -{ - stbi__context s; - stbi__start_file(&s,f); - return stbi_test_main(&s); -} - -int stbi_test(char const *filename) -{ - FILE *f = fopen(filename, "rb"); - int result; - if (!f) return STBI_unknown; - result = stbi_test_from_file(f); - fclose(f); - return result; -} -#endif //!STBI_NO_STDIO - -int stbi_test_from_memory(stbi_uc const *buffer, int len) -{ - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi_test_main(&s); -} - -int stbi_test_from_callbacks(stbi_io_callbacks const *clbk, void *user) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi_test_main(&s); -} diff --git a/lib/SOIL2/stbi_pkm.h b/lib/SOIL2/stbi_pkm.h deleted file mode 100644 index 92f064080..000000000 --- a/lib/SOIL2/stbi_pkm.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - adding PKM loading support to stbi -*/ - -#ifndef HEADER_STB_IMAGE_PKM_AUGMENTATION -#define HEADER_STB_IMAGE_PKM_AUGMENTATION - -/* is it a PKM file? */ -extern int stbi__pkm_test_memory (stbi_uc const *buffer, int len); -extern int stbi__pkm_test_callbacks (stbi_io_callbacks const *clbk, void *user); - -extern void *stbi__pkm_load_from_path (char const *filename, int *x, int *y, int *comp, int req_comp); -extern void *stbi__pkm_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); -extern void *stbi__pkm_load_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp); - -#ifndef STBI_NO_STDIO -extern int stbi__pkm_test_filename (char const *filename); -extern int stbi__pkm_test_file (FILE *f); -extern void *stbi__pkm_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); -#endif - -extern int stbi__pkm_info_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp); -extern int stbi__pkm_info_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); - - -#ifndef STBI_NO_STDIO -extern int stbi__pkm_info_from_path (char const *filename, int *x, int *y, int *comp); -extern int stbi__pkm_info_from_file (FILE *f, int *x, int *y, int *comp); -#endif - -/* -// -//// end header file /////////////////////////////////////////////////////*/ -#endif /* HEADER_STB_IMAGE_PKM_AUGMENTATION */ diff --git a/lib/SOIL2/stbi_pkm_c.h b/lib/SOIL2/stbi_pkm_c.h deleted file mode 100644 index a05182cb1..000000000 --- a/lib/SOIL2/stbi_pkm_c.h +++ /dev/null @@ -1,227 +0,0 @@ -#include "pkm_helper.h" -#include "etc1_utils.h" - -static int stbi__pkm_test(stbi__context *s) -{ - // check the magic number - if (stbi__get8(s) != 'P') { - stbi__rewind(s); - return 0; - } - - if (stbi__get8(s) != 'K') { - stbi__rewind(s); - return 0; - } - - if (stbi__get8(s) != 'M') { - stbi__rewind(s); - return 0; - } - - if (stbi__get8(s) != ' ') { - stbi__rewind(s); - return 0; - } - - if (stbi__get8(s) != '1') { - stbi__rewind(s); - return 0; - } - - if (stbi__get8(s) != '0') { - stbi__rewind(s); - return 0; - } - - stbi__rewind(s); - return 1; -} - -#ifndef STBI_NO_STDIO - -int stbi__pkm_test_filename (char const *filename) -{ - int r; - FILE *f = fopen(filename, "rb"); - if (!f) return 0; - r = stbi__pkm_test_file(f); - fclose(f); - return r; -} - -int stbi__pkm_test_file (FILE *f) -{ - stbi__context s; - int r,n = ftell(f); - stbi__start_file(&s,f); - r = stbi__pkm_test(&s); - fseek(f,n,SEEK_SET); - return r; -} -#endif - -int stbi__pkm_test_memory (stbi_uc const *buffer, int len) -{ - stbi__context s; - stbi__start_mem(&s,buffer, len); - return stbi__pkm_test(&s); -} - -int stbi__pkm_test_callbacks (stbi_io_callbacks const *clbk, void *user) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi__pkm_test(&s); -} - -static int stbi__pkm_info(stbi__context *s, int *x, int *y, int *comp ) -{ - PKMHeader header; - unsigned int width, height; - - stbi__getn( s, (stbi_uc*)(&header), sizeof(PKMHeader) ); - - if ( 0 != strcmp( header.aName, "PKM 10" ) ) { - stbi__rewind(s); - return 0; - } - - width = (header.iWidthMSB << 8) | header.iWidthLSB; - height = (header.iHeightMSB << 8) | header.iHeightLSB; - - *x = s->img_x = width; - *y = s->img_y = height; - *comp = s->img_n = 3; - - stbi__rewind(s); - - return 1; -} - -int stbi__pkm_info_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp ) -{ - stbi__context s; - stbi__start_mem(&s,buffer, len); - return stbi__pkm_info( &s, x, y, comp ); -} - -int stbi__pkm_info_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi__pkm_info( &s, x, y, comp ); -} - -#ifndef STBI_NO_STDIO -int stbi__pkm_info_from_path(char const *filename, int *x, int *y, int *comp) -{ - int res; - FILE *f = fopen(filename, "rb"); - if (!f) return 0; - res = stbi__pkm_info_from_file( f, x, y, comp ); - fclose(f); - return res; -} - -int stbi__pkm_info_from_file(FILE *f, int *x, int *y, int *comp) -{ - stbi__context s; - int res; - long n = ftell(f); - stbi__start_file(&s, f); - res = stbi__pkm_info(&s, x, y, comp); - fseek(f, n, SEEK_SET); - return res; -} -#endif - -static void * stbi__pkm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - stbi_uc *pkm_data = NULL; - stbi_uc *pkm_res_data = NULL; - PKMHeader header; - unsigned int width; - unsigned int height; - unsigned int align = 0; - unsigned int bpr; - unsigned int size; - unsigned int compressedSize; - - int res; - - stbi__getn( s, (stbi_uc*)(&header), sizeof(PKMHeader) ); - - if ( 0 != strcmp( header.aName, "PKM 10" ) ) { - return NULL; - } - - width = (header.iWidthMSB << 8) | header.iWidthLSB; - height = (header.iHeightMSB << 8) | header.iHeightLSB; - - *x = s->img_x = width; - *y = s->img_y = height; - *comp = s->img_n = 3; - - compressedSize = etc1_get_encoded_data_size(width, height); - - pkm_data = (stbi_uc *)malloc(compressedSize); - stbi__getn( s, pkm_data, compressedSize ); - - bpr = ((width * 3) + align) & ~align; - size = bpr * height; - pkm_res_data = (stbi_uc *)malloc(size); - - res = etc1_decode_image((const etc1_byte*)pkm_data, (etc1_byte*)pkm_res_data, width, height, 3, bpr); - - free( pkm_data ); - - if ( 0 == res ) { - if( (req_comp <= 4) && (req_comp >= 1) ) { - // user has some requirements, meet them - if( req_comp != s->img_n ) { - pkm_res_data = stbi__convert_format( pkm_res_data, s->img_n, req_comp, s->img_x, s->img_y ); - *comp = req_comp; - } - } - - return (stbi_uc *)pkm_res_data; - } else { - free( pkm_res_data ); - } - - return NULL; -} - -#ifndef STBI_NO_STDIO -void *stbi__pkm_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_file(&s,f); - return stbi__pkm_load(&s,x,y,comp,req_comp); -} - -void *stbi__pkm_load_from_path (char const*filename, int *x, int *y, int *comp, int req_comp) -{ - void *data; - FILE *f = fopen(filename, "rb"); - if (!f) return NULL; - data = stbi__pkm_load_from_file(f,x,y,comp,req_comp); - fclose(f); - return data; -} -#endif - -void *stbi__pkm_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_mem(&s,buffer, len); - return stbi__pkm_load(&s,x,y,comp,req_comp); -} - -void *stbi__pkm_load_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi__pkm_load(&s,x,y,comp,req_comp); -} diff --git a/lib/SOIL2/stbi_pvr.h b/lib/SOIL2/stbi_pvr.h deleted file mode 100644 index e982715a8..000000000 --- a/lib/SOIL2/stbi_pvr.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - adding PVR loading support to stbi -*/ - -#ifndef HEADER_STB_IMAGE_PVR_AUGMENTATION -#define HEADER_STB_IMAGE_PVR_AUGMENTATION - -/* is it a PVR file? */ -extern int stbi__pvr_test_memory (stbi_uc const *buffer, int len); -extern int stbi__pvr_test_callbacks (stbi_io_callbacks const *clbk, void *user); - -extern void *stbi__pvr_load_from_path (char const *filename, int *x, int *y, int *comp, int req_comp); -extern void *stbi__pvr_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); -extern void *stbi__pvr_load_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp); - -#ifndef STBI_NO_STDIO -extern int stbi__pvr_test_filename (char const *filename); -extern int stbi__pvr_test_file (FILE *f); -extern void *stbi__pvr_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); -#endif - -extern int stbi__pvr_info_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int *iscompressed); -extern int stbi__pvr_info_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int *iscompressed); - - -#ifndef STBI_NO_STDIO -extern int stbi__pvr_info_from_path (char const *filename, int *x, int *y, int *comp, int *iscompressed); -extern int stbi__pvr_info_from_file (FILE *f, int *x, int *y, int *comp, int *iscompressed); -#endif - -/* -// -//// end header file /////////////////////////////////////////////////////*/ -#endif /* HEADER_STB_IMAGE_PVR_AUGMENTATION */ diff --git a/lib/SOIL2/stbi_pvr_c.h b/lib/SOIL2/stbi_pvr_c.h deleted file mode 100644 index 991746526..000000000 --- a/lib/SOIL2/stbi_pvr_c.h +++ /dev/null @@ -1,1001 +0,0 @@ -#include "pvr_helper.h" - -static int stbi__pvr_test(stbi__context *s) -{ - // check header size - if (stbi__get32le(s) != sizeof(PVR_Texture_Header)) { - stbi__rewind(s); - return 0; - } - - // stbi__skip until the magic number - stbi__skip(s, 10*4); - - // check the magic number - if ( stbi__get32le(s) != PVRTEX_IDENTIFIER ) { - stbi__rewind(s); - return 0; - } - - // Also rewind because the loader needs to read the header - stbi__rewind(s); - - return 1; -} - -#ifndef STBI_NO_STDIO - -int stbi__pvr_test_filename (char const *filename) -{ - int r; - FILE *f = fopen(filename, "rb"); - if (!f) return 0; - r = stbi__pvr_test_file(f); - fclose(f); - return r; -} - -int stbi__pvr_test_file (FILE *f) -{ - stbi__context s; - int r,n = ftell(f); - stbi__start_file(&s,f); - r = stbi__pvr_test(&s); - fseek(f,n,SEEK_SET); - return r; -} -#endif - -int stbi__pvr_test_memory (stbi_uc const *buffer, int len) -{ - stbi__context s; - stbi__start_mem(&s,buffer, len); - return stbi__pvr_test(&s); -} - -int stbi__pvr_test_callbacks (stbi_io_callbacks const *clbk, void *user) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi__pvr_test(&s); -} - -static int stbi__pvr_info(stbi__context *s, int *x, int *y, int *comp, int * iscompressed ) -{ - PVR_Texture_Header header={0}; - - stbi__getn( s, (stbi_uc*)(&header), sizeof(PVR_Texture_Header) ); - - // Check the header size - if ( header.dwHeaderSize != sizeof(PVR_Texture_Header) ) { - stbi__rewind( s ); - return 0; - } - - // Check the magic identifier - if ( header.dwPVR != PVRTEX_IDENTIFIER ) { - stbi__rewind(s); - return 0; - } - - *x = s->img_x = header.dwWidth; - *y = s->img_y = header.dwHeight; - *comp = s->img_n = ( header.dwBitCount + 7 ) / 8; - - if ( iscompressed ) - *iscompressed = 0; - - switch ( header.dwpfFlags & PVRTEX_PIXELTYPE ) - { - case OGL_RGBA_4444: - s->img_n = 2; - break; - case OGL_RGBA_5551: - s->img_n = 2; - break; - case OGL_RGBA_8888: - s->img_n = 4; - break; - case OGL_RGB_565: - s->img_n = 2; - break; - case OGL_RGB_888: - s->img_n = 3; - break; - case OGL_I_8: - s->img_n = 1; - break; - case OGL_AI_88: - s->img_n = 2; - break; - case OGL_PVRTC2: - s->img_n = 4; - if ( iscompressed ) - *iscompressed = 1; - break; - case OGL_PVRTC4: - s->img_n = 4; - if ( iscompressed ) - *iscompressed = 1; - break; - case OGL_RGB_555: - default: - stbi__rewind(s); - return 0; - } - - *comp = s->img_n; - - return 1; -} - -int stbi__pvr_info_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int * iscompressed ) -{ - stbi__context s; - stbi__start_mem(&s,buffer, len); - return stbi__pvr_info( &s, x, y, comp, iscompressed ); -} - -int stbi__pvr_info_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int * iscompressed) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi__pvr_info( &s, x, y, comp, iscompressed ); -} - -#ifndef STBI_NO_STDIO -int stbi__pvr_info_from_path(char const *filename, int *x, int *y, int *comp, int * iscompressed) -{ - int res; - FILE *f = fopen(filename, "rb"); - if (!f) return 0; - res = stbi__pvr_info_from_file( f, x, y, comp, iscompressed ); - fclose(f); - return res; -} - -int stbi__pvr_info_from_file(FILE *f, int *x, int *y, int *comp, int * iscompressed) -{ - stbi__context s; - int res; - long n = ftell(f); - stbi__start_file(&s, f); - res = stbi__pvr_info(&s, x, y, comp, iscompressed); - fseek(f, n, SEEK_SET); - return res; -} -#endif - -/****************************************************************************** - Taken from: - @File PVRTDecompress.cpp - @Title PVRTDecompress - @Copyright Copyright (C) Imagination Technologies Limited. - @Platform ANSI compatible - @Description PVRTC Texture Decompression. -******************************************************************************/ - -typedef unsigned char PVRTuint8; -typedef unsigned short PVRTuint16; -typedef unsigned int PVRTuint32; - -/***************************************************************************** - * defines and consts - *****************************************************************************/ -#define PT_INDEX (2) // The Punch-through index - -#define BLK_Y_SIZE (4) // always 4 for all 2D block types - -#define BLK_X_MAX (8) // Max X dimension for blocks - -#define BLK_X_2BPP (8) // dimensions for the two formats -#define BLK_X_4BPP (4) - -#define WRAP_COORD(Val, Size) ((Val) & ((Size)-1)) - -#define POWER_OF_2(X) util_number_is_power_2(X) - -/* - Define an expression to either wrap or clamp large or small vals to the - legal coordinate range -*/ -#define PVRT_MIN(a,b) (((a) < (b)) ? (a) : (b)) -#define PVRT_MAX(a,b) (((a) > (b)) ? (a) : (b)) -#define PVRT_CLAMP(x, l, h) (PVRT_MIN((h), PVRT_MAX((x), (l)))) - -#define LIMIT_COORD(Val, Size, AssumeImageTiles) \ - ((AssumeImageTiles)? WRAP_COORD((Val), (Size)): PVRT_CLAMP((Val), 0, (Size)-1)) - -/***************************************************************************** - * Useful typedefs - *****************************************************************************/ -typedef PVRTuint32 U32; -typedef PVRTuint8 U8; - -/*********************************************************** - DECOMPRESSION ROUTINES -************************************************************/ - -/*!*********************************************************************** - @Struct AMTC_BLOCK_STRUCT - @Brief -*************************************************************************/ -typedef struct -{ - // Uses 64 bits pre block - U32 PackedData[2]; -}AMTC_BLOCK_STRUCT; - - /*!*********************************************************************** - @Function util_number_is_power_2 - @Input input A number - @Returns TRUE if the number is an integer power of two, else FALSE. - @Description Check that a number is an integer power of two, i.e. - 1, 2, 4, 8, ... etc. - Returns FALSE for zero. -*************************************************************************/ -int util_number_is_power_2( unsigned input ) -{ - unsigned minus1; - - if( !input ) return 0; - - minus1 = input - 1; - return ( (input | minus1) == (input ^ minus1) ) ? 1 : 0; -} - -/*!*********************************************************************** - @Function Unpack5554Colour - @Input pBlock - @Input ABColours - @Description Given a block, extract the colour information and convert - to 5554 formats -*************************************************************************/ -static void Unpack5554Colour(const AMTC_BLOCK_STRUCT *pBlock, - int ABColours[2][4]) -{ - U32 RawBits[2]; - - int i; - - // Extract A and B - RawBits[0] = pBlock->PackedData[1] & (0xFFFE); // 15 bits (shifted up by one) - RawBits[1] = pBlock->PackedData[1] >> 16; // 16 bits - - // step through both colours - for(i = 0; i < 2; i++) - { - // If completely opaque - if(RawBits[i] & (1<<15)) - { - // Extract R and G (both 5 bit) - ABColours[i][0] = (RawBits[i] >> 10) & 0x1F; - ABColours[i][1] = (RawBits[i] >> 5) & 0x1F; - - /* - The precision of Blue depends on A or B. If A then we need to - replicate the top bit to get 5 bits in total - */ - ABColours[i][2] = RawBits[i] & 0x1F; - if(i==0) - { - ABColours[0][2] |= ABColours[0][2] >> 4; - } - - // set 4bit alpha fully on... - ABColours[i][3] = 0xF; - } - else // Else if colour has variable translucency - { - /* - Extract R and G (both 4 bit). - (Leave a space on the end for the replication of bits - */ - ABColours[i][0] = (RawBits[i] >> (8-1)) & 0x1E; - ABColours[i][1] = (RawBits[i] >> (4-1)) & 0x1E; - - // replicate bits to truly expand to 5 bits - ABColours[i][0] |= ABColours[i][0] >> 4; - ABColours[i][1] |= ABColours[i][1] >> 4; - - // grab the 3(+padding) or 4 bits of blue and add an extra padding bit - ABColours[i][2] = (RawBits[i] & 0xF) << 1; - - /* - expand from 3 to 5 bits if this is from colour A, or 4 to 5 bits if from - colour B - */ - if(i==0) - { - ABColours[0][2] |= ABColours[0][2] >> 3; - } - else - { - ABColours[0][2] |= ABColours[0][2] >> 4; - } - - // Set the alpha bits to be 3 + a zero on the end - ABColours[i][3] = (RawBits[i] >> 11) & 0xE; - } - } -} - -/*!*********************************************************************** - @Function UnpackModulations - @Input pBlock - @Input Do2bitMode - @Input ModulationVals - @Input ModulationModes - @Input StartX - @Input StartY - @Description Given the block and the texture type and it's relative - position in the 2x2 group of blocks, extract the bit - patterns for the fully defined pixels. -*************************************************************************/ -static void UnpackModulations(const AMTC_BLOCK_STRUCT *pBlock, - const int Do2bitMode, - int ModulationVals[8][16], - int ModulationModes[8][16], - int StartX, - int StartY) -{ - int BlockModMode; - U32 ModulationBits; - - int x, y; - - BlockModMode= pBlock->PackedData[1] & 1; - ModulationBits = pBlock->PackedData[0]; - - // if it's in an interpolated mode - if(Do2bitMode && BlockModMode) - { - /* - run through all the pixels in the block. Note we can now treat all the - "stored" values as if they have 2bits (even when they didn't!) - */ - for(y = 0; y < BLK_Y_SIZE; y++) - { - for(x = 0; x < BLK_X_2BPP; x++) - { - ModulationModes[y+StartY][x+StartX] = BlockModMode; - - // if this is a stored value... - if(((x^y)&1) == 0) - { - ModulationVals[y+StartY][x+StartX] = ModulationBits & 3; - ModulationBits >>= 2; - } - } - } - } - else if(Do2bitMode) // else if direct encoded 2bit mode - i.e. 1 mode bit per pixel - { - for(y = 0; y < BLK_Y_SIZE; y++) - { - for(x = 0; x < BLK_X_2BPP; x++) - { - ModulationModes[y+StartY][x+StartX] = BlockModMode; - - // double the bits so 0=> 00, and 1=>11 - if(ModulationBits & 1) - { - ModulationVals[y+StartY][x+StartX] = 0x3; - } - else - { - ModulationVals[y+StartY][x+StartX] = 0x0; - } - ModulationBits >>= 1; - } - } - } - else // else its the 4bpp mode so each value has 2 bits - { - for(y = 0; y < BLK_Y_SIZE; y++) - { - for(x = 0; x < BLK_X_4BPP; x++) - { - ModulationModes[y+StartY][x+StartX] = BlockModMode; - - ModulationVals[y+StartY][x+StartX] = ModulationBits & 3; - ModulationBits >>= 2; - } - } - } - - // make sure nothing is left over - assert(ModulationBits==0); -} - -/*!*********************************************************************** - @Function InterpolateColours - @Input ColourP - @Input ColourQ - @Input ColourR - @Input ColourS - @Input Do2bitMode - @Input x - @Input y - @Modified Result - @Description This performs a HW bit accurate interpolation of either the - A or B colours for a particular pixel. - - NOTE: It is assumed that the source colours are in ARGB 5554 - format - This means that some "preparation" of the values will - be necessary. -*************************************************************************/ -static void InterpolateColours(const int ColourP[4], - const int ColourQ[4], - const int ColourR[4], - const int ColourS[4], - const int Do2bitMode, - const int x, - const int y, - int Result[4]) -{ - int u, v, uscale; - int k; - - int tmp1, tmp2; - - int P[4], Q[4], R[4], S[4]; - - // Copy the colours - for(k = 0; k < 4; k++) - { - P[k] = ColourP[k]; - Q[k] = ColourQ[k]; - R[k] = ColourR[k]; - S[k] = ColourS[k]; - } - - // put the x and y values into the right range - v = (y & 0x3) | ((~y & 0x2) << 1); - - if(Do2bitMode) - u = (x & 0x7) | ((~x & 0x4) << 1); - else - u = (x & 0x3) | ((~x & 0x2) << 1); - - // get the u and v scale amounts - v = v - BLK_Y_SIZE/2; - - if(Do2bitMode) - { - u = u - BLK_X_2BPP/2; - uscale = 8; - } - else - { - u = u - BLK_X_4BPP/2; - uscale = 4; - } - - for(k = 0; k < 4; k++) - { - tmp1 = P[k] * uscale + u * (Q[k] - P[k]); - tmp2 = R[k] * uscale + u * (S[k] - R[k]); - - tmp1 = tmp1 * 4 + v * (tmp2 - tmp1); - - Result[k] = tmp1; - } - - // Lop off the appropriate number of bits to get us to 8 bit precision - if(Do2bitMode) - { - // do RGB - for(k = 0; k < 3; k++) - { - Result[k] >>= 2; - } - - Result[3] >>= 1; - } - else - { - // do RGB (A is ok) - for(k = 0; k < 3; k++) - { - Result[k] >>= 1; - } - } - - // sanity check - for(k = 0; k < 4; k++) - { - assert(Result[k] < 256); - } - - - /* - Convert from 5554 to 8888 - - do RGB 5.3 => 8 - */ - for(k = 0; k < 3; k++) - { - Result[k] += Result[k] >> 5; - } - - Result[3] += Result[3] >> 4; - - // 2nd sanity check - for(k = 0; k < 4; k++) - { - assert(Result[k] < 256); - } - -} - -/*!*********************************************************************** - @Function GetModulationValue - @Input x - @Input y - @Input Do2bitMode - @Input ModulationVals - @Input ModulationModes - @Input Mod - @Input DoPT - @Description Get the modulation value as a numerator of a fraction of 8ths -*************************************************************************/ -static void GetModulationValue(int x, - int y, - const int Do2bitMode, - const int ModulationVals[8][16], - const int ModulationModes[8][16], - int *Mod, - int *DoPT) -{ - static const int RepVals0[4] = {0, 3, 5, 8}; - static const int RepVals1[4] = {0, 4, 4, 8}; - - int ModVal; - - // Map X and Y into the local 2x2 block - y = (y & 0x3) | ((~y & 0x2) << 1); - - if(Do2bitMode) - x = (x & 0x7) | ((~x & 0x4) << 1); - else - x = (x & 0x3) | ((~x & 0x2) << 1); - - // assume no PT for now - *DoPT = 0; - - // extract the modulation value. If a simple encoding - if(ModulationModes[y][x]==0) - { - ModVal = RepVals0[ModulationVals[y][x]]; - } - else if(Do2bitMode) - { - // if this is a stored value - if(((x^y)&1)==0) - ModVal = RepVals0[ModulationVals[y][x]]; - else if(ModulationModes[y][x] == 1) // else average from the neighbours if H&V interpolation.. - { - ModVal = (RepVals0[ModulationVals[y-1][x]] + - RepVals0[ModulationVals[y+1][x]] + - RepVals0[ModulationVals[y][x-1]] + - RepVals0[ModulationVals[y][x+1]] + 2) / 4; - } - else if(ModulationModes[y][x] == 2) // else if H-Only - { - ModVal = (RepVals0[ModulationVals[y][x-1]] + - RepVals0[ModulationVals[y][x+1]] + 1) / 2; - } - else // else it's V-Only - { - ModVal = (RepVals0[ModulationVals[y-1][x]] + - RepVals0[ModulationVals[y+1][x]] + 1) / 2; - } - } - else // else it's 4BPP and PT encoding - { - ModVal = RepVals1[ModulationVals[y][x]]; - - *DoPT = ModulationVals[y][x] == PT_INDEX; - } - - *Mod =ModVal; -} - -/*!*********************************************************************** - @Function TwiddleUV - @Input YSize Y dimension of the texture in pixels - @Input XSize X dimension of the texture in pixels - @Input YPos Pixel Y position - @Input XPos Pixel X position - @Returns The twiddled offset of the pixel - @Description Given the Block (or pixel) coordinates and the dimension of - the texture in blocks (or pixels) this returns the twiddled - offset of the block (or pixel) from the start of the map. - - NOTE the dimensions of the texture must be a power of 2 -*************************************************************************/ -static int DisableTwiddlingRoutine = 0; - -static U32 TwiddleUV(U32 YSize, U32 XSize, U32 YPos, U32 XPos) -{ - U32 Twiddled; - - U32 MinDimension; - U32 MaxValue; - - U32 SrcBitPos; - U32 DstBitPos; - - int ShiftCount; - - assert(YPos < YSize); - assert(XPos < XSize); - - assert(POWER_OF_2(YSize)); - assert(POWER_OF_2(XSize)); - - if(YSize < XSize) - { - MinDimension = YSize; - MaxValue = XPos; - } - else - { - MinDimension = XSize; - MaxValue = YPos; - } - - // Nasty hack to disable twiddling - if(DisableTwiddlingRoutine) - return (YPos* XSize + XPos); - - // Step through all the bits in the "minimum" dimension - SrcBitPos = 1; - DstBitPos = 1; - Twiddled = 0; - ShiftCount = 0; - - while(SrcBitPos < MinDimension) - { - if(YPos & SrcBitPos) - { - Twiddled |= DstBitPos; - } - - if(XPos & SrcBitPos) - { - Twiddled |= (DstBitPos << 1); - } - - - SrcBitPos <<= 1; - DstBitPos <<= 2; - ShiftCount += 1; - - } - - // prepend any unused bits - MaxValue >>= ShiftCount; - - Twiddled |= (MaxValue << (2*ShiftCount)); - - return Twiddled; -} - -/***********************************************************/ -/* -// Decompress -// -// Takes the compressed input data and outputs the equivalent decompressed -// image. -*/ -/***********************************************************/ - -static void Decompress(AMTC_BLOCK_STRUCT *pCompressedData, - const int Do2bitMode, - const int XDim, - const int YDim, - const int AssumeImageTiles, - unsigned char* pResultImage) -{ - int x, y; - int i, j; - - int BlkX, BlkY; - int BlkXp1, BlkYp1; - int XBlockSize; - int BlkXDim, BlkYDim; - - int StartX, StartY; - - int ModulationVals[8][16]; - int ModulationModes[8][16]; - - int Mod, DoPT; - - unsigned int uPosition; - - /* - // local neighbourhood of blocks - */ - AMTC_BLOCK_STRUCT *pBlocks[2][2]; - - AMTC_BLOCK_STRUCT *pPrevious[2][2] = {{NULL, NULL}, {NULL, NULL}}; - - /* - // Low precision colours extracted from the blocks - */ - struct - { - int Reps[2][4]; - }Colours5554[2][2]; - - /* - // Interpolated A and B colours for the pixel - */ - int ASig[4], BSig[4]; - - int Result[4]; - - if(Do2bitMode) - { - XBlockSize = BLK_X_2BPP; - } - else - { - XBlockSize = BLK_X_4BPP; - } - - - /* - // For MBX don't allow the sizes to get too small - */ - BlkXDim = PVRT_MAX(2, XDim / XBlockSize); - BlkYDim = PVRT_MAX(2, YDim / BLK_Y_SIZE); - - /* - // Step through the pixels of the image decompressing each one in turn - // - // Note that this is a hideously inefficient way to do this! - */ - for(y = 0; y < YDim; y++) - { - for(x = 0; x < XDim; x++) - { - /* - // map this pixel to the top left neighbourhood of blocks - */ - BlkX = (x - XBlockSize/2); - BlkY = (y - BLK_Y_SIZE/2); - - BlkX = LIMIT_COORD(BlkX, XDim, AssumeImageTiles); - BlkY = LIMIT_COORD(BlkY, YDim, AssumeImageTiles); - - - BlkX /= XBlockSize; - BlkY /= BLK_Y_SIZE; - - //BlkX = LIMIT_COORD(BlkX, BlkXDim, AssumeImageTiles); - //BlkY = LIMIT_COORD(BlkY, BlkYDim, AssumeImageTiles); - - - /* - // compute the positions of the other 3 blocks - */ - BlkXp1 = LIMIT_COORD(BlkX+1, BlkXDim, AssumeImageTiles); - BlkYp1 = LIMIT_COORD(BlkY+1, BlkYDim, AssumeImageTiles); - - /* - // Map to block memory locations - */ - pBlocks[0][0] = pCompressedData +TwiddleUV(BlkYDim, BlkXDim, BlkY, BlkX); - pBlocks[0][1] = pCompressedData +TwiddleUV(BlkYDim, BlkXDim, BlkY, BlkXp1); - pBlocks[1][0] = pCompressedData +TwiddleUV(BlkYDim, BlkXDim, BlkYp1, BlkX); - pBlocks[1][1] = pCompressedData +TwiddleUV(BlkYDim, BlkXDim, BlkYp1, BlkXp1); - - - /* - // extract the colours and the modulation information IF the previous values - // have changed. - */ - if(memcmp(pPrevious, pBlocks, 4*sizeof(void*)) != 0) - { - StartY = 0; - for(i = 0; i < 2; i++) - { - StartX = 0; - for(j = 0; j < 2; j++) - { - Unpack5554Colour(pBlocks[i][j], Colours5554[i][j].Reps); - - UnpackModulations(pBlocks[i][j], - Do2bitMode, - ModulationVals, - ModulationModes, - StartX, StartY); - - StartX += XBlockSize; - }/*end for j*/ - - StartY += BLK_Y_SIZE; - }/*end for i*/ - - /* - // make a copy of the new pointers - */ - memcpy(pPrevious, pBlocks, 4*sizeof(void*)); - }/*end if the blocks have changed*/ - - - /* - // decompress the pixel. First compute the interpolated A and B signals - */ - InterpolateColours(Colours5554[0][0].Reps[0], - Colours5554[0][1].Reps[0], - Colours5554[1][0].Reps[0], - Colours5554[1][1].Reps[0], - Do2bitMode, x, y, - ASig); - - InterpolateColours(Colours5554[0][0].Reps[1], - Colours5554[0][1].Reps[1], - Colours5554[1][0].Reps[1], - Colours5554[1][1].Reps[1], - Do2bitMode, x, y, - BSig); - - GetModulationValue(x,y, Do2bitMode, (const int (*)[16])ModulationVals, (const int (*)[16])ModulationModes, - &Mod, &DoPT); - - /* - // compute the modulated colour - */ - for(i = 0; i < 4; i++) - { - Result[i] = ASig[i] * 8 + Mod * (BSig[i] - ASig[i]); - Result[i] >>= 3; - } - if(DoPT) - { - Result[3] = 0; - } - - /* - // Store the result in the output image - */ - uPosition = (x+y*XDim)<<2; - pResultImage[uPosition+0] = (unsigned char)Result[0]; - pResultImage[uPosition+1] = (unsigned char)Result[1]; - pResultImage[uPosition+2] = (unsigned char)Result[2]; - pResultImage[uPosition+3] = (unsigned char)Result[3]; - - }/*end for x*/ - }/*end for y*/ - -} - -static void * stbi__pvr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - stbi_uc *pvr_data = NULL; - stbi_uc *pvr_res_data = NULL; - PVR_Texture_Header header={0}; - int iscompressed = 0; - int bitmode = 0; - unsigned int levelSize = 0; - - stbi__getn( s, (stbi_uc*)(&header), sizeof(PVR_Texture_Header) ); - - // Check the header size - if ( header.dwHeaderSize != sizeof(PVR_Texture_Header) ) { - return NULL; - } - - // Check the magic identifier - if ( header.dwPVR != PVRTEX_IDENTIFIER ) { - return NULL; - } - - *x = s->img_x = header.dwWidth; - *y = s->img_y = header.dwHeight; - - /* Get if the texture is compressed and the texture mode ( 2bpp or 4bpp ) */ - switch ( header.dwpfFlags & PVRTEX_PIXELTYPE ) - { - case OGL_RGBA_4444: - s->img_n = 2; - break; - case OGL_RGBA_5551: - s->img_n = 2; - break; - case OGL_RGBA_8888: - s->img_n = 4; - break; - case OGL_RGB_565: - s->img_n = 2; - break; - case OGL_RGB_888: - s->img_n = 3; - break; - case OGL_I_8: - s->img_n = 1; - break; - case OGL_AI_88: - s->img_n = 2; - break; - case OGL_PVRTC2: - bitmode = 1; - s->img_n = 4; - iscompressed = 1; - break; - case OGL_PVRTC4: - s->img_n = 4; - iscompressed = 1; - break; - case OGL_RGB_555: - default: - return NULL; - } - - *comp = s->img_n; - - // Load only the first mip map level - levelSize = (s->img_x * s->img_y * header.dwBitCount + 7) / 8; - - // get the raw data - pvr_data = (stbi_uc *)malloc( levelSize ); - stbi__getn( s, pvr_data, levelSize ); - - // if compressed decompress as RGBA - if ( iscompressed ) { - pvr_res_data = (stbi_uc *)malloc( s->img_x * s->img_y * 4 ); - Decompress( (AMTC_BLOCK_STRUCT*)pvr_data, bitmode, s->img_x, s->img_y, 1, (unsigned char*)pvr_res_data ); - free( pvr_data ); - } else { - // otherwise use the raw data - pvr_res_data = pvr_data; - } - - if( (req_comp <= 4) && (req_comp >= 1) ) { - // user has some requirements, meet them - if( req_comp != s->img_n ) { - pvr_res_data = stbi__convert_format( pvr_res_data, s->img_n, req_comp, s->img_x, s->img_y ); - *comp = req_comp; - } - } - - return pvr_res_data; -} - -#ifndef STBI_NO_STDIO -void *stbi__pvr_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_file(&s,f); - return stbi__pvr_load(&s,x,y,comp,req_comp); -} - -void *stbi__pvr_load_from_path (char const*filename, int *x, int *y, int *comp, int req_comp) -{ - void *data; - FILE *f = fopen(filename, "rb"); - if (!f) return NULL; - data = stbi__pvr_load_from_file(f,x,y,comp,req_comp); - fclose(f); - return data; -} -#endif - -void *stbi__pvr_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_mem(&s,buffer, len); - return stbi__pvr_load(&s,x,y,comp,req_comp); -} - -void *stbi__pvr_load_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi__pvr_load(&s,x,y,comp,req_comp); -} diff --git a/src/gl/gltex.cpp b/src/gl/gltex.cpp index 28a1985f0..bb64e9798 100644 --- a/src/gl/gltex.cpp +++ b/src/gl/gltex.cpp @@ -40,8 +40,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -#include - #include #include #include @@ -96,13 +94,16 @@ void initializeTextureUnits( const QOpenGLContext * context ) set_max_anisotropy(); //qDebug() << "maximum anisotropy" << max_anisotropy; } + #ifdef WIN32 if ( !glActiveTextureARB ) - glActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC)SOIL_GL_GetProcAddress( "glActiveTextureARB" ); + glActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC)context->getProcAddress( "glActiveTextureARB" ); if ( !glClientActiveTextureARB ) - glClientActiveTextureARB = (PFNGLCLIENTACTIVETEXTUREARBPROC)SOIL_GL_GetProcAddress( "glClientActiveTextureARB" ); + glClientActiveTextureARB = (PFNGLCLIENTACTIVETEXTUREARBPROC)context->getProcAddress( "glClientActiveTextureARB" ); #endif + + initializeTextureLoaders( context ); } bool activateTextureUnit( int stage ) diff --git a/src/gl/gltexloaders.cpp b/src/gl/gltexloaders.cpp index a4b3ec17b..2432bed62 100644 --- a/src/gl/gltexloaders.cpp +++ b/src/gl/gltexloaders.cpp @@ -36,7 +36,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "model/nifmodel.h" #include "dds.h" -#include #include #include @@ -626,34 +625,16 @@ GLuint texLoadBMP( QIODevice & f, QString & texformat, GLenum & target, GLuint & GLuint texLoadDDS( const QString & filepath, QString & format, GLenum & target, GLuint & width, GLuint & height, GLuint & mipmaps, QByteArray & data, GLuint & id ) { - if ( !extInitialized ) { -#ifndef __APPLE__ - glTexStorage2D = (PFNGLTEXSTORAGE2DPROC)SOIL_GL_GetProcAddress( "glTexStorage2D" ); -#ifdef _WIN32 - glCompressedTexSubImage2D = (PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC)SOIL_GL_GetProcAddress( "glCompressedTexSubImage2D" ); -#endif -#endif - if ( !glTexStorage2D || !glCompressedTexSubImage2D ) - extStorageSupported = false; - - extInitialized = true; - } - GLuint result = 0; gli::texture texture; if ( extStorageSupported ) { texture = load_if_valid( data.constData(), data.size() ); if ( !texture.empty() ) result = GLI_create_texture( texture, target, id ); - } else { -#ifdef _WIN32 - glCompressedTexImage2D = (PFNGLCOMPRESSEDTEXIMAGE2DPROC)SOIL_GL_GetProcAddress( "glCompressedTexImage2D" ); -#endif - if ( glCompressedTexImage2D ) { - texture = load_if_valid( data.constData(), data.size() ); - if ( !texture.empty() ) - result = GLI_create_texture_fallback( texture, target, id ); - } + } else if ( glCompressedTexImage2D ) { + texture = load_if_valid( data.constData(), data.size() ); + if ( !texture.empty() ) + result = GLI_create_texture_fallback( texture, target, id ); } if ( result ) { @@ -863,6 +844,23 @@ GLuint texLoadNIF( QIODevice & f, QString & texformat, GLenum & target, GLuint & return mipmaps; } +//! Initialize the GL functions necessary for texture loading +void initializeTextureLoaders( const QOpenGLContext * context ) +{ + if ( !extInitialized ) { +#ifndef __APPLE__ + glTexStorage2D = (PFNGLTEXSTORAGE2DPROC)context->getProcAddress( "glTexStorage2D" ); +#ifdef _WIN32 + glCompressedTexSubImage2D = (PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC)context->getProcAddress( "glCompressedTexSubImage2D" ); + glCompressedTexImage2D = (PFNGLCOMPRESSEDTEXIMAGE2DPROC)context->getProcAddress( "glCompressedTexImage2D" ); +#endif +#endif + if ( !glTexStorage2D || !glCompressedTexSubImage2D ) + extStorageSupported = false; + + extInitialized = true; + } +} //! Create texture with glTexStorage2D using GLI GLuint GLI_create_texture( gli::texture& texture, GLenum& target, GLuint& id ) diff --git a/src/gl/gltexloaders.h b/src/gl/gltexloaders.h index 456debc2a..738f9727e 100644 --- a/src/gl/gltexloaders.h +++ b/src/gl/gltexloaders.h @@ -35,6 +35,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "gli.hpp" +class QOpenGLContext; class QByteArray; class QModelIndex; class QString; @@ -42,8 +43,13 @@ class QString; typedef unsigned int GLuint; typedef unsigned int GLenum; +//! Initialize the GL functions necessary for texture loading +extern void initializeTextureLoaders( const QOpenGLContext * context ); +//! Create texture with glTexStorage2D using GLI extern GLuint GLI_create_texture( gli::texture& texture, GLenum& target, GLuint& id ); +//! Fallback for systems that do not have glTexStorage2D extern GLuint GLI_create_texture_fallback( gli::texture& texture, GLenum & target, GLuint& id ); +//! Rewrite of gli::load_dds to not crash on invalid textures extern gli::texture load_if_valid( const char * data, int size ); //! @file gltexloaders.h Texture loading functions header From c6eb2ad3e62492bb6543427b917de2abf9b74b67 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Mon, 11 Dec 2017 11:06:56 -0500 Subject: [PATCH 125/152] [Build] GCC and Clang warning reduction Used `-isystem` to try to suppress warnings for libs on non-msvc compilers. For msvc, at least wrapped gli.hpp inclusion in a pragma. Used clang output from previous Travis builds to drastically lower the number of warnings. --- .gitignore | 1 + NifSkope.pro | 65 +++++++++--------------------------- NifSkope_functions.pri | 3 +- src/data/niftypes.h | 2 +- src/gl/bsshape.cpp | 8 ++--- src/gl/controllers.cpp | 4 +-- src/gl/controllers.h | 2 +- src/gl/glmesh.cpp | 4 ++- src/gl/gltexloaders.cpp | 4 +-- src/gl/gltexloaders.h | 12 +++++-- src/glview.h | 2 +- src/spells/animation.cpp | 2 +- src/spells/blocks.cpp | 10 +++--- src/spells/blocks.h | 6 ++-- src/spells/color.cpp | 8 ++--- src/spells/flags.cpp | 9 +++-- src/spells/fo3only.cpp | 2 +- src/spells/headerstring.cpp | 4 +-- src/spells/light.cpp | 4 +-- src/spells/materialedit.cpp | 4 +-- src/spells/misc.cpp | 8 ++--- src/spells/normals.cpp | 2 +- src/spells/sanitize.cpp | 10 +++--- src/spells/stringpalette.cpp | 8 ++--- src/spells/texture.cpp | 4 +-- src/spells/transform.cpp | 4 +-- src/ui/settingspane.cpp | 5 ++- src/ui/widgets/floatslider.h | 4 +-- src/ui/widgets/nifview.h | 4 +-- src/ui/widgets/valueedit.h | 4 +-- 30 files changed, 91 insertions(+), 118 deletions(-) diff --git a/.gitignore b/.gitignore index 4a64e58e0..23c75da21 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ ui_*.h # Qt Creator NifSkope.pro.user +.qmake.stash # Binaries NifSkope diff --git a/NifSkope.pro b/NifSkope.pro index af9b8bc0a..4109884b8 100644 --- a/NifSkope.pro +++ b/NifSkope.pro @@ -330,23 +330,15 @@ nvtristrip { } qhull { - INCLUDEPATH += lib/qhull/src - HEADERS += \ - lib/qhull/src/libqhull/geom.h \ - lib/qhull/src/libqhull/io.h \ - lib/qhull/src/libqhull/libqhull.h \ - lib/qhull/src/libqhull/mem.h \ - lib/qhull/src/libqhull/merge.h \ - lib/qhull/src/libqhull/poly.h \ - lib/qhull/src/libqhull/qhull_a.h \ - lib/qhull/src/libqhull/qset.h \ - lib/qhull/src/libqhull/random.h \ - lib/qhull/src/libqhull/stat.h \ - lib/qhull/src/libqhull/user.h + !*msvc*:QMAKE_CFLAGS += -isystem ../nifskope/lib/qhull/src + !*msvc*:QMAKE_CXXFLAGS += -isystem ../nifskope/lib/qhull/src + else:INCLUDEPATH += lib/qhull/src + HEADERS += $$files($$PWD/lib/qhull/src/libqhull/*.h, false) } gli { - INCLUDEPATH += lib/gli/gli lib/gli/external + !*msvc*:QMAKE_CXXFLAGS += -isystem ../nifskope/lib/gli/gli -isystem ../nifskope/lib/gli/external + else:INCLUDEPATH += lib/gli/gli lib/gli/external HEADERS += $$files($$PWD/lib/gli/gli/*.hpp, true) HEADERS += $$files($$PWD/lib/gli/gli/*.inl, true) HEADERS += $$files($$PWD/lib/gli/external/glm/*.hpp, true) @@ -354,37 +346,11 @@ gli { } zlib { - INCLUDEPATH += lib/zlib - - HEADERS += \ - lib/zlib/crc32.h \ - lib/zlib/deflate.h \ - lib/zlib/gzguts.h \ - lib/zlib/inffast.h \ - lib/zlib/inffixed.h \ - lib/zlib/inflate.h \ - lib/zlib/inftrees.h \ - lib/zlib/trees.h \ - lib/zlib/zconf.h \ - lib/zlib/zlib.h \ - lib/zlib/zutil.h - - SOURCES += \ - lib/zlib/adler32.c \ - lib/zlib/compress.c \ - lib/zlib/crc32.c \ - lib/zlib/deflate.c \ - lib/zlib/gzclose.c \ - lib/zlib/gzlib.c \ - lib/zlib/gzread.c \ - lib/zlib/gzwrite.c \ - lib/zlib/infback.c \ - lib/zlib/inffast.c \ - lib/zlib/inflate.c \ - lib/zlib/inftrees.c \ - lib/zlib/trees.c \ - lib/zlib/uncompr.c \ - lib/zlib/zutil.c + !*msvc*:QMAKE_CFLAGS += -isystem ../nifskope/lib/zlib + !*msvc*:QMAKE_CXXFLAGS += -isystem ../nifskope/lib/zlib + else:INCLUDEPATH += lib/zlib + HEADERS += $$files($$PWD/lib/zlib/*.h, false) + SOURCES += $$files($$PWD/lib/zlib/*.c, false) } lz4 { @@ -438,6 +404,11 @@ win32 { # Multithreaded compiling for Visual Studio QMAKE_CXXFLAGS += -MP + # Standards conformance to match GCC and clang + !isEmpty(_MSC_VER):greaterThan(_MSC_VER, 1900) { + QMAKE_CXXFLAGS += /permissive- /std:c++latest + } + # LINKER FLAGS # Relocate .lib and .exp files to keep release dir clean @@ -445,10 +416,6 @@ win32 { # PDB location QMAKE_LFLAGS_DEBUG += /PDB:$$syspath($${INTERMEDIATE}/nifskope.pdb) - - # Clean up .embed.manifest from release dir - # Fallback for `Manifest Embed` above - QMAKE_POST_LINK += $$QMAKE_DEL_FILE $$syspath($${DESTDIR}/*.manifest) $$nt } diff --git a/NifSkope_functions.pri b/NifSkope_functions.pri index 430306336..ad95f8136 100644 --- a/NifSkope_functions.pri +++ b/NifSkope_functions.pri @@ -259,7 +259,8 @@ defineTest(copyDirs) { # Fix copy for subdir on unix, also assure clean subdirs (no extra files) !isEmpty(subdir) { - win32:QMAKE_POST_LINK += rd /s /q $${ddir} $$nt + win32:*msvc*:QMAKE_POST_LINK += rd /s /q $${ddir} $$nt + else:!unix:QMAKE_POST_LINK += rm -rf $${ddir} $$nt unix:QMAKE_POST_LINK += rm -rf $${ddir} $$nt } diff --git a/src/data/niftypes.h b/src/data/niftypes.h index 344e5287c..9bd43ba28 100644 --- a/src/data/niftypes.h +++ b/src/data/niftypes.h @@ -1748,7 +1748,7 @@ class BSVertexDesc void SetAttributeOffset( VertexAttribute attr, uint offset ) { if ( attr != VA_POSITION ) { - desc = ((uint64_t)offset << (4 * (uchar)attr + 2)) | desc & ~(15 << (4 * (uchar)attr + 4)); + desc = ((uint64_t)offset << (4 * (uchar)attr + 2)) | (desc & ~(15 << (4 * (uchar)attr + 4))); } } diff --git a/src/gl/bsshape.cpp b/src/gl/bsshape.cpp index 1c885647d..2a732db5c 100644 --- a/src/gl/bsshape.cpp +++ b/src/gl/bsshape.cpp @@ -770,8 +770,7 @@ void BSShape::drawSelection() const auto suboff = subrec.child( o, 2 ).data().toInt() / 3; auto subcnt = subrec.child( o + 1, 2 ).data().toInt(); - int j = suboff; - for ( j; j < subcnt + suboff; j++ ) { + for ( int j = suboff; j < subcnt + suboff; j++ ) { if ( j >= maxTris ) continue; @@ -788,9 +787,8 @@ void BSShape::drawSelection() const // Sub-segmentless Segments if ( numRec == 0 && cnt > 0 ) { glColor( Color4( cols.value( (idx.row() + l) % 7 ) ) ); - - int i = off; - for ( i; i < cnt + off; i++ ) { + + for ( int i = off; i < cnt + off; i++ ) { if ( i >= maxTris ) continue; diff --git a/src/gl/controllers.cpp b/src/gl/controllers.cpp index aa6050a3d..5ffb58b7a 100644 --- a/src/gl/controllers.cpp +++ b/src/gl/controllers.cpp @@ -156,7 +156,7 @@ void ControllerManager::setSequence( const QString & seqname ) continue; if ( ctrltype == "NiTransformController" && multiTargetTransformer ) { - if ( multiTargetTransformer->setInterpolator( node, iInterp ) ) { + if ( multiTargetTransformer->setInterpolatorNode( node, iInterp ) ) { multiTargetTransformer->start = start; multiTargetTransformer->stop = stop; multiTargetTransformer->phase = phase; @@ -325,7 +325,7 @@ bool MultiTargetTransformController::update( const NifModel * nif, const QModelI return false; } -bool MultiTargetTransformController::setInterpolator( Node * node, const QModelIndex & idx ) +bool MultiTargetTransformController::setInterpolatorNode( Node * node, const QModelIndex & idx ) { const NifModel * nif = static_cast(idx.model()); diff --git a/src/gl/controllers.h b/src/gl/controllers.h index 30ab83277..110221bde 100644 --- a/src/gl/controllers.h +++ b/src/gl/controllers.h @@ -108,7 +108,7 @@ class MultiTargetTransformController final : public Controller bool update( const NifModel * nif, const QModelIndex & index ) override final; - bool setInterpolator( Node * node, const QModelIndex & idx ); + bool setInterpolatorNode( Node * node, const QModelIndex & idx ); protected: QPointer target; diff --git a/src/gl/glmesh.cpp b/src/gl/glmesh.cpp index 4076f49ca..f345703eb 100644 --- a/src/gl/glmesh.cpp +++ b/src/gl/glmesh.cpp @@ -188,7 +188,7 @@ void Mesh::update( const NifModel * nif, const QModelIndex & index ) updateData |= updateSkin; doSkinning = doSkinningCurr; - if ( !iBlock.isValid() || !index.isValid() && !updateSkin ) + if ( (!iBlock.isValid() || !index.isValid()) && !updateSkin ) return; if ( !isBSLODPresent ) { @@ -473,6 +473,8 @@ void Mesh::transform() case NiMesh::E_BINORMAL_BP: bitangents.append( tempValue.get() ); break; + default: + break; } break; case NiMesh::F_UINT16_1: diff --git a/src/gl/gltexloaders.cpp b/src/gl/gltexloaders.cpp index 2432bed62..e0d152529 100644 --- a/src/gl/gltexloaders.cpp +++ b/src/gl/gltexloaders.cpp @@ -898,7 +898,6 @@ GLuint GLI_create_texture( gli::texture& texture, GLenum& target, GLuint& id ) for ( size_t layer = 0; layer < texture.layers(); ++layer ) for ( size_t face = 0; face < texture.faces(); ++face ) for ( size_t level = 0; level < texture.levels(); ++level ) { - GLsizei const layerGL = static_cast(layer); glm::tvec3 textureLevelExtent( texture.extent( level ) ); switch ( texture.target() ) { case gli::TARGET_2D: @@ -955,7 +954,6 @@ GLuint GLI_create_texture_fallback( gli::texture& texture, GLenum & target, GLui for ( std::size_t layer = 0; layer < texture.layers(); ++layer ) for ( std::size_t face = 0; face < texture.faces(); ++face ) for ( std::size_t level = 0; level < texture.levels(); ++level ) { - GLsizei const layerGL = static_cast(layer); glm::tvec3 extent( texture.extent( level ) ); switch ( texture.target() ) { case gli::TARGET_2D: @@ -989,7 +987,7 @@ GLuint GLI_create_texture_fallback( gli::texture& texture, GLenum & target, GLui } //! Rewrite of gli::load_dds to not crash on invalid textures -gli::texture load_if_valid( const char * data, int size ) +gli::texture load_if_valid( const char * data, unsigned int size ) { using namespace gli; using namespace gli::detail; diff --git a/src/gl/gltexloaders.h b/src/gl/gltexloaders.h index 738f9727e..1cb71c55c 100644 --- a/src/gl/gltexloaders.h +++ b/src/gl/gltexloaders.h @@ -33,7 +33,15 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef GLTEXLOADERS_H #define GLTEXLOADERS_H -#include "gli.hpp" +#ifdef _MSC_VER +#pragma warning(push, 0) +#endif + +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif class QOpenGLContext; class QByteArray; @@ -50,7 +58,7 @@ extern GLuint GLI_create_texture( gli::texture& texture, GLenum& target, GLuint& //! Fallback for systems that do not have glTexStorage2D extern GLuint GLI_create_texture_fallback( gli::texture& texture, GLenum & target, GLuint& id ); //! Rewrite of gli::load_dds to not crash on invalid textures -extern gli::texture load_if_valid( const char * data, int size ); +extern gli::texture load_if_valid( const char * data, unsigned int size ); //! @file gltexloaders.h Texture loading functions header diff --git a/src/glview.h b/src/glview.h index 757a06e0e..acfea7d74 100644 --- a/src/glview.h +++ b/src/glview.h @@ -285,7 +285,7 @@ class GLGraphicsView : public QGraphicsView ~GLGraphicsView(); protected slots: - virtual void setupViewport( QWidget * viewport ); + void setupViewport( QWidget * viewport ) override; protected: bool eventFilter( QObject * o, QEvent * e ) override final; diff --git a/src/spells/animation.cpp b/src/spells/animation.cpp index e585de754..3e724a9b0 100644 --- a/src/spells/animation.cpp +++ b/src/spells/animation.cpp @@ -374,7 +374,7 @@ class spFixAVObjectPalette final : public Spell } - QModelIndex cast( NifModel * nif, const QModelIndex & index ) + QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final { auto iHeader = nif->getHeader(); auto numStrings = nif->get( iHeader, "Num Strings" ); diff --git a/src/spells/blocks.cpp b/src/spells/blocks.cpp index cf9d0d7ec..7c4b901d4 100644 --- a/src/spells/blocks.cpp +++ b/src/spells/blocks.cpp @@ -479,7 +479,7 @@ class spCopyBlock final : public Spell public: QString name() const override final { return Spell::tr( "Copy" ); } QString page() const override final { return Spell::tr( "Block" ); } - QKeySequence hotkey() const { return{ Qt::CTRL + Qt::SHIFT + Qt::Key_C }; } + QKeySequence hotkey() const override final { return{ Qt::CTRL + Qt::SHIFT + Qt::Key_C }; } bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { @@ -575,7 +575,7 @@ class spPasteOverBlock final : public Spell public: QString name() const override final { return Spell::tr( "Paste Over" ); } QString page() const override final { return Spell::tr( "Block" ); } - QKeySequence hotkey() const { return{ Qt::CTRL + Qt::SHIFT + Qt::Key_V }; } + QKeySequence hotkey() const override final { return{ Qt::CTRL + Qt::SHIFT + Qt::Key_V }; } QString acceptFormat( const QString & format, const NifModel * nif, const QModelIndex & block ) { @@ -941,7 +941,7 @@ class spMoveBlockUp final : public Spell public: QString name() const override final { return Spell::tr( "Move Up" ); } QString page() const override final { return Spell::tr( "Block" ); } - QKeySequence hotkey() const { return { Qt::CTRL + Qt::Key_Up }; } + QKeySequence hotkey() const override final { return { Qt::CTRL + Qt::Key_Up }; } bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { @@ -964,7 +964,7 @@ class spMoveBlockDown final : public Spell public: QString name() const override final { return Spell::tr( "Move Down" ); } QString page() const override final { return Spell::tr( "Block" ); } - QKeySequence hotkey() const { return { Qt::CTRL + Qt::Key_Down }; } + QKeySequence hotkey() const override final { return { Qt::CTRL + Qt::Key_Down }; } bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { @@ -1151,7 +1151,7 @@ class spDuplicateBlock final : public Spell public: QString name() const override final { return Spell::tr( "Duplicate" ); } QString page() const override final { return Spell::tr( "Block" ); } - QKeySequence hotkey() const { return{ Qt::CTRL + Qt::SHIFT + Qt::Key_D }; } + QKeySequence hotkey() const override final { return{ Qt::CTRL + Qt::SHIFT + Qt::Key_D }; } bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { diff --git a/src/spells/blocks.h b/src/spells/blocks.h index 18031dfa8..65cc64456 100644 --- a/src/spells/blocks.h +++ b/src/spells/blocks.h @@ -17,7 +17,7 @@ class spCopyBranch final : public Spell public: QString name() const override final { return Spell::tr( "Copy Branch" ); } QString page() const override final { return Spell::tr( "Block" ); } - QKeySequence hotkey() const { return QKeySequence( QKeySequence::Copy ); } + QKeySequence hotkey() const override final { return QKeySequence( QKeySequence::Copy ); } bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final; QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final; @@ -30,7 +30,7 @@ class spPasteBranch final : public Spell QString name() const override final { return Spell::tr( "Paste Branch" ); } QString page() const override final { return Spell::tr( "Block" ); } // Doesn't work unless the menu entry is unique - QKeySequence hotkey() const { return QKeySequence( QKeySequence::Paste ); } + QKeySequence hotkey() const override final { return QKeySequence( QKeySequence::Paste ); } QString acceptFormat( const QString & format, const NifModel * nif ); @@ -44,7 +44,7 @@ class spDuplicateBranch final : public Spell public: QString name() const override final { return Spell::tr( "Duplicate Branch" ); } QString page() const override final { return Spell::tr( "Block" ); } - QKeySequence hotkey() const { return{ Qt::CTRL + Qt::Key_D }; } + QKeySequence hotkey() const override final { return{ Qt::CTRL + Qt::Key_D }; } bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final; diff --git a/src/spells/color.cpp b/src/spells/color.cpp index b23d731b4..cc205421c 100644 --- a/src/spells/color.cpp +++ b/src/spells/color.cpp @@ -16,8 +16,8 @@ class spChooseColor final : public Spell public: QString name() const override final { return Spell::tr( "Choose" ); } QString page() const override final { return Spell::tr( "Color" ); } - QIcon icon() const { return ColorWheel::getIcon(); } - bool instant() const { return true; } + QIcon icon() const override final { return ColorWheel::getIcon(); } + bool instant() const override final { return true; } bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { @@ -49,8 +49,8 @@ class spSetAllColor final : public Spell public: QString name() const override final { return Spell::tr( "Set All" ); } QString page() const override final { return Spell::tr( "Color" ); } - QIcon icon() const { return ColorWheel::getIcon(); } - bool instant() const { return true; } + QIcon icon() const override final { return ColorWheel::getIcon(); } + bool instant() const override final { return true; } bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { diff --git a/src/spells/flags.cpp b/src/spells/flags.cpp index 81481ec73..36df2fd5c 100644 --- a/src/spells/flags.cpp +++ b/src/spells/flags.cpp @@ -21,8 +21,8 @@ class spEditFlags : public Spell { public: QString name() const override { return Spell::tr( "Flags" ); } - bool instant() const { return true; } - QIcon icon() const { return QIcon( ":/img/flag" ); } + bool instant() const override { return true; } + QIcon icon() const override { return QIcon( ":/img/flag" ); } //! Node / Property types on which flags are applicable enum FlagType @@ -965,8 +965,8 @@ class spEditVertexDesc final : public spEditFlags { public: QString name() const override final { return Spell::tr( "Vertex Flags" ); } - bool instant() const { return true; } - QIcon icon() const { return QIcon( ":/img/flag" ); } + bool instant() const override final { return true; } + QIcon icon() const override final { return QIcon( ":/img/flag" ); } bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { @@ -979,7 +979,6 @@ class spEditVertexDesc final : public spEditFlags uint stream = nif->getUserVersion2(); bool dynamic = nif->inherits( index.parent(), "BSDynamicTriShape" ); - ushort flags = desc.GetFlags(); QStringList flagNames { Spell::tr( "Vertex" ), // VA_POSITION = 0x0, Spell::tr( "UVs" ), // VA_TEXCOORD0 = 0x1, diff --git a/src/spells/fo3only.cpp b/src/spells/fo3only.cpp index 9cb1bd423..8409cf45b 100644 --- a/src/spells/fo3only.cpp +++ b/src/spells/fo3only.cpp @@ -14,7 +14,7 @@ class spFO3FixShapeDataName final : public Spell public: QString name() const override final { return Spell::tr( "Fix Geometry Data Names" ); } QString page() const override final { return Spell::tr( "Sanitize" ); } - bool sanity() const { return true; } + bool sanity() const override final { return true; } ////////////////////////////////////////////////////////////////////////// // Valid if nothing or NiGeometryData-based node is selected diff --git a/src/spells/headerstring.cpp b/src/spells/headerstring.cpp index a6cd022b5..9e0a36638 100644 --- a/src/spells/headerstring.cpp +++ b/src/spells/headerstring.cpp @@ -68,14 +68,14 @@ class spEditStringIndex final : public Spell public: QString name() const override final { return Spell::tr( "Edit String Index" ); } QString page() const override final { return Spell::tr( "" ); } - QIcon icon() const + QIcon icon() const override final { if ( !txt_xpm_icon ) txt_xpm_icon = QIconPtr( new QIcon(QPixmap( txt_xpm )) ); return *txt_xpm_icon; } - bool instant() const { return true; } + bool instant() const override final { return true; } bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { diff --git a/src/spells/light.cpp b/src/spells/light.cpp index 7c4ba6934..1db937a1d 100644 --- a/src/spells/light.cpp +++ b/src/spells/light.cpp @@ -57,8 +57,8 @@ class spLightEdit final : public Spell public: QString name() const override final { return Spell::tr( "Light" ); } QString page() const override final { return Spell::tr( "" ); } - bool instant() const { return true; } - QIcon icon() const + bool instant() const override final { return true; } + QIcon icon() const override final { if ( !light42_xpm_icon ) light42_xpm_icon = QIconPtr( new QIcon(QPixmap( light42_xpm )) ); diff --git a/src/spells/materialedit.cpp b/src/spells/materialedit.cpp index 5a0cf00c2..b9b40716c 100644 --- a/src/spells/materialedit.cpp +++ b/src/spells/materialedit.cpp @@ -97,8 +97,8 @@ class spMaterialEdit final : public Spell public: QString name() const override final { return Spell::tr( "Material" ); } QString page() const override final { return Spell::tr( "" ); } - bool instant() const { return true; } - QIcon icon() const + bool instant() const override final { return true; } + QIcon icon() const override final { if ( !mat42_xpm_icon ) mat42_xpm_icon = QIconPtr( new QIcon(QPixmap( mat42_xpm )) ); diff --git a/src/spells/misc.cpp b/src/spells/misc.cpp index 6f2f5d8fc..ed2814a3f 100644 --- a/src/spells/misc.cpp +++ b/src/spells/misc.cpp @@ -15,8 +15,8 @@ class spUpdateArray final : public Spell public: QString name() const override final { return Spell::tr( "Update" ); } QString page() const override final { return Spell::tr( "Array" ); } - QIcon icon() const { return QIcon( ":/img/update" ); } - bool instant() const { return true; } + QIcon icon() const override final { return QIcon( ":/img/update" ); } + bool instant() const override final { return true; } bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { @@ -104,8 +104,8 @@ class spFollowLink final : public Spell { public: QString name() const override final { return Spell::tr( "Follow Link" ); } - bool instant() const { return true; } - QIcon icon() const { return QIcon( ":/img/link" ); } + bool instant() const override final { return true; } + QIcon icon() const override final { return QIcon( ":/img/link" ); } bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { diff --git a/src/spells/normals.cpp b/src/spells/normals.cpp index c2d32555d..f66291928 100644 --- a/src/spells/normals.cpp +++ b/src/spells/normals.cpp @@ -194,7 +194,7 @@ class spSmoothNormals final : public Spell QVector verts; QVector norms; - int numVerts; + int numVerts = 0; if ( nif->getUserVersion2() < 100 ) { verts = nif->getArray( iData, "Vertices" ); diff --git a/src/spells/sanitize.cpp b/src/spells/sanitize.cpp index 64ede7c9b..b87adf64f 100644 --- a/src/spells/sanitize.cpp +++ b/src/spells/sanitize.cpp @@ -22,7 +22,7 @@ class spReorderLinks final : public Spell public: QString name() const override final { return Spell::tr( "Reorder Link Arrays" ); } QString page() const override final { return Spell::tr( "Sanitize" ); } - bool sanity() const { return true; } + bool sanity() const override final { return true; } bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { @@ -85,7 +85,7 @@ class spSanitizeLinkArrays final : public Spell public: QString name() const override final { return Spell::tr( "Collapse Link Arrays" ); } QString page() const override final { return Spell::tr( "Sanitize" ); } - bool sanity() const { return true; } + bool sanity() const override final { return true; } bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { @@ -133,7 +133,7 @@ class spAdjustTextureSources final : public Spell public: QString name() const override final { return Spell::tr( "Adjust Texture Sources" ); } QString page() const override final { return Spell::tr( "Sanitize" ); } - bool sanity() const { return true; } + bool sanity() const override final { return true; } bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { @@ -269,7 +269,7 @@ class spSanityCheckLinks final : public Spell public: QString name() const override final { return Spell::tr( "Check Links" ); } QString page() const override final { return Spell::tr( "Sanitize" ); } - bool sanity() const { return true; } + bool sanity() const override final { return true; } bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { @@ -344,7 +344,7 @@ class spFixInvalidNames final : public Spell public: QString name() const override final { return Spell::tr( "Fix Invalid Block Names" ); } QString page() const override final { return Spell::tr( "Sanitize" ); } - bool sanity() const { return true; } + bool sanity() const override final { return true; } bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { diff --git a/src/spells/stringpalette.cpp b/src/spells/stringpalette.cpp index ed37030c7..29d330d11 100644 --- a/src/spells/stringpalette.cpp +++ b/src/spells/stringpalette.cpp @@ -73,14 +73,14 @@ class spEditStringOffset final : public Spell public: QString name() const override final { return Spell::tr( "Edit String Offset" ); } QString page() const override final { return Spell::tr( "" ); } - QIcon icon() const + QIcon icon() const override final { if ( !txt_xpm_icon ) txt_xpm_icon = QIconPtr( new QIcon(QPixmap( txt_xpm )) ); return *txt_xpm_icon; } - bool instant() const { return true; } + bool instant() const override final { return true; } bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { @@ -290,7 +290,7 @@ class spEditStringEntries final : public Spell QString name() const override final { return Spell::tr( "Replace Entries" ); } QString page() const override final { return Spell::tr( "String Palette" ); } - bool instant() const { return false; } + bool instant() const override final { return false; } bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { @@ -449,7 +449,7 @@ class spStringPaletteLister final : public Spell QString name() const override final { return Spell::tr( "Edit String Palettes" ); } QString page() const override final { return Spell::tr( "Animation" ); } - bool instant() const { return false; } + bool instant() const override final { return false; } bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { diff --git a/src/spells/texture.cpp b/src/spells/texture.cpp index 94be733fd..388331eeb 100644 --- a/src/spells/texture.cpp +++ b/src/spells/texture.cpp @@ -159,8 +159,8 @@ class spChooseTexture final : public Spell public: QString name() const override final { return Spell::tr( "Choose" ); } QString page() const override final { return Spell::tr( "Texture" ); } - bool instant() const { return true; } - QIcon icon() const + bool instant() const override final { return true; } + QIcon icon() const override final { if ( !tex42_xpm_icon ) tex42_xpm_icon = QIconPtr( new QIcon(QPixmap( tex42_xpm )) ); diff --git a/src/spells/transform.cpp b/src/spells/transform.cpp index bb3204d8f..c43dec0bd 100644 --- a/src/spells/transform.cpp +++ b/src/spells/transform.cpp @@ -349,8 +349,8 @@ class spEditTransformation final : public Spell public: QString name() const override final { return Spell::tr( "Edit" ); } QString page() const override final { return Spell::tr( "Transform" ); } - bool instant() const { return true; } - QIcon icon() const + bool instant() const override final { return true; } + QIcon icon() const override final { if ( !transform_xpm_icon ) transform_xpm_icon = QIconPtr( new QIcon(QPixmap( transform_xpm )) ); diff --git a/src/ui/settingspane.cpp b/src/ui/settingspane.cpp index 95744f8f3..8b03a59c4 100644 --- a/src/ui/settingspane.cpp +++ b/src/ui/settingspane.cpp @@ -464,11 +464,10 @@ void SettingsRender::setDefault() read(); } - +#ifdef Q_OS_WIN32 bool regFolderPath( QStringList & gamePaths, const QString & regPath, const QString & regValue, const QString & gameFolder, QStringList gameSubDirs = QStringList(), QStringList gameArchiveFilters = QStringList() ) { -#ifdef Q_OS_WIN32 QSettings reg( regPath, QSettings::Registry32Format ); QDir dir( reg.value( regValue ).toString() ); @@ -495,7 +494,6 @@ bool regFolderPath( QStringList & gamePaths, const QString & regPath, const QStr } return true; } -#endif return false; } @@ -508,6 +506,7 @@ bool regFolderPaths( QStringList & gamePaths, const QStringList & regPaths, cons } return result; } +#endif /* * Resources diff --git a/src/ui/widgets/floatslider.h b/src/ui/widgets/floatslider.h index 8c960316c..37dc67c77 100644 --- a/src/ui/widgets/floatslider.h +++ b/src/ui/widgets/floatslider.h @@ -83,9 +83,9 @@ class FloatSlider : public QWidget void addEditor( QWidget * ); //! The recommended size of the widget; reimplemented from QWidget - QSize sizeHint() const; + QSize sizeHint() const override; //! The recommended minimum size of the widget; reimplemented from QWidget - QSize minimumSizeHint() const; + QSize minimumSizeHint() const override; signals: void valueChanged( float ); diff --git a/src/ui/widgets/nifview.h b/src/ui/widgets/nifview.h index 749c94655..7e8f820c5 100644 --- a/src/ui/widgets/nifview.h +++ b/src/ui/widgets/nifview.h @@ -71,7 +71,7 @@ class NifTreeView final : public QTreeView public slots: //! Sets the root index - void setRootIndex( const QModelIndex & index ); + void setRootIndex( const QModelIndex & index ) override final; //! Clear the root index; probably conncted to NifSkope::dList void clearRootIndex(); @@ -84,7 +84,7 @@ protected slots: //! Recursively updates version conditions void updateConditionRecurse( const QModelIndex & index ); //! Called when the current index changes - void currentChanged( const QModelIndex & current, const QModelIndex & previous ); + void currentChanged( const QModelIndex & current, const QModelIndex & previous ) override final; //! Scroll to index; connected to expanded() void scrollExpand( const QModelIndex & index ); diff --git a/src/ui/widgets/valueedit.h b/src/ui/widgets/valueedit.h index d565d94f9..fb2520262 100644 --- a/src/ui/widgets/valueedit.h +++ b/src/ui/widgets/valueedit.h @@ -288,9 +288,9 @@ public slots: void setValue( uint value ); protected: - QString textFromValue( int value ) const; + QString textFromValue( int value ) const override; - int valueFromText( const QString & text ) const; + int valueFromText( const QString & text ) const override; uint toUInt( int i ) const { From c788e1fce6c234380aa43707f0e51778fc15efb7 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Fri, 15 Dec 2017 10:35:55 -0500 Subject: [PATCH 126/152] [UI] Auto-link pasted NiCollisionObjects on selected block --- src/spells/blocks.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/spells/blocks.cpp b/src/spells/blocks.cpp index 7c4b901d4..333345e9b 100644 --- a/src/spells/blocks.cpp +++ b/src/spells/blocks.cpp @@ -125,6 +125,8 @@ void blockLink( NifModel * nif, const QModelIndex & index, const QModelIndex & i nif->setLink( index, "Next Controller", nif->getBlockNumber( iBlock ) ); nif->setLink( iBlock, "Target", nif->getLink( index, "Target" ) ); } + } else if ( nif->inherits( index, "NiAVObject" ) && nif->inherits( iBlock, "NiCollisionObject" ) ) { + nif->setLink( index, "Collision Object", nif->getBlockNumber( iBlock ) ); } } From 4d6f4b7be7f1f06f5d1b987b08459766a67032ca Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Fri, 15 Dec 2017 10:49:13 -0500 Subject: [PATCH 127/152] nifxml 0.9 sync, manual externalcond for BSVertexData Since BSVertexDesc is an internal type, need to special-case the arg attribute for the Vertex Desc. Also forced `externalcond` on BSVertexData as it's still not part of the XML spec and that way I don't have to worry about including it in the packaged XML for FO4/SSE to not be brutally slow. --- src/xml/nifxml.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/xml/nifxml.cpp b/src/xml/nifxml.cpp index 37e6a8bdc..182e0629c 100644 --- a/src/xml/nifxml.cpp +++ b/src/xml/nifxml.cpp @@ -203,7 +203,7 @@ class NifXmlHandler final : public QXmlDefaultHandler } QString externalCond = list.value( "externalcond" ); - if ( externalCond == "1" ) { + if ( externalCond == "1" || blk->id.startsWith( "BSVertexData" ) ) { NifModel::fixedCompounds.insert( blk->id, blk ); } } @@ -268,6 +268,7 @@ class NifXmlHandler final : public QXmlDefaultHandler { QString type = list.value( "type" ); QString tmpl = list.value( "template" ); + QString arg = list.value( "arg" ); QString arr1 = list.value( "arr1" ); QString arr2 = list.value( "arr2" ); QString cond = list.value( "cond" ); @@ -310,13 +311,18 @@ class NifXmlHandler final : public QXmlDefaultHandler isCompound = !isMixin; } + // Special casing for BSVertexDesc as an ARG + // since we have internalized the type + if ( arg == "Vertex Desc\\Vertex Attributes" ) + arg = "Vertex Desc"; + // now allocate data = NifData( list.value( "name" ), type, tmpl, NifValue( NifValue::type( type ) ), - list.value( "arg" ), + arg, arr1, arr2, cond, From e34252eb1a2d5b64a33834a190b6dc2d4141233f Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Fri, 15 Dec 2017 11:09:55 -0500 Subject: [PATCH 128/152] [UI] Fix UI painting issues for Qt 5.10 setCentralWidget before setViewport breaks in 5.10. Also moved the resize timer to constantly restart as you resize to fix an issue where the framebuffer snapshot could become black. --- src/nifskope.cpp | 5 +++-- src/nifskope_ui.cpp | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/nifskope.cpp b/src/nifskope.cpp index abc7ea25b..5f12fd834 100644 --- a/src/nifskope.cpp +++ b/src/nifskope.cpp @@ -290,10 +290,11 @@ NifSkope::NifSkope() graphicsView->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); //graphicsView->setOptimizationFlags( QGraphicsView::DontSavePainterState | QGraphicsView::DontAdjustForAntialiasing ); - // Set central widget and viewport - setCentralWidget( graphicsView ); graphicsView->setViewport( ogl ); graphicsView->setViewportUpdateMode( QGraphicsView::FullViewportUpdate ); + + // Set central widget and viewport + setCentralWidget( graphicsView ); setContextMenuPolicy( Qt::NoContextMenu ); diff --git a/src/nifskope_ui.cpp b/src/nifskope_ui.cpp index 05b4ae720..403cabe6d 100644 --- a/src/nifskope_ui.cpp +++ b/src/nifskope_ui.cpp @@ -1270,9 +1270,10 @@ bool NifSkope::eventFilter( QObject * o, QEvent * e ) ogl->setDisabled( true ); isResizing = true; - resizeTimer->start( 300 ); } + resizeTimer->start( 300 ); + return true; } From a7f9cc6a889691351d11ac3a6a04ad288a6af7ce Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Fri, 15 Dec 2017 12:22:52 -0500 Subject: [PATCH 129/152] [UI] Default layout and size tweaks Also fixed Tree/List view bug with loading NIFs while in List mode. --- src/gl/glnode.cpp | 3 +- src/gl/glnode.h | 1 - src/glview.cpp | 2 +- src/nifskope.cpp | 4 +- src/nifskope_ui.cpp | 6 ++ src/ui/nifskope.ui | 214 +++++++++++++++++++-------------------- src/ui/widgets/nifview.h | 2 +- 7 files changed, 117 insertions(+), 115 deletions(-) diff --git a/src/gl/glnode.cpp b/src/gl/glnode.cpp index a42585c2d..d9cbb9da3 100644 --- a/src/gl/glnode.cpp +++ b/src/gl/glnode.cpp @@ -207,8 +207,7 @@ void Node::updateSettings() { QSettings settings; settings.beginGroup( "Settings/Render/Colors/" ); - - cfg.background = settings.value( "Background", QColor( 0, 0, 0 ) ).value(); + // TODO: Remove the registry read for every new Node cfg.highlight = settings.value( "Highlight", QColor( 255, 255, 0 ) ).value(); cfg.wireframe = settings.value( "Wireframe", QColor( 0, 255, 0 ) ).value(); diff --git a/src/gl/glnode.h b/src/gl/glnode.h index 3c170e0bb..0bbcc8a84 100644 --- a/src/gl/glnode.h +++ b/src/gl/glnode.h @@ -165,7 +165,6 @@ public slots: struct Settings { - QColor background; QColor highlight; QColor wireframe; } cfg; diff --git a/src/glview.cpp b/src/glview.cpp index bd2a62c0c..2d3108181 100644 --- a/src/glview.cpp +++ b/src/glview.cpp @@ -233,7 +233,7 @@ void GLView::updateSettings() QSettings settings; settings.beginGroup( "Settings/Render" ); - cfg.background = settings.value( "Colors/Background", QColor( 0, 0, 0 ) ).value(); + cfg.background = settings.value( "Colors/Background", QColor( 46, 46, 46 ) ).value(); cfg.fov = settings.value( "General/Camera/Field Of View" ).toFloat(); cfg.moveSpd = settings.value( "General/Camera/Movement Speed" ).toFloat(); cfg.rotSpd = settings.value( "General/Camera/Rotation Speed" ).toFloat(); diff --git a/src/nifskope.cpp b/src/nifskope.cpp index 5f12fd834..011a9f99f 100644 --- a/src/nifskope.cpp +++ b/src/nifskope.cpp @@ -201,7 +201,7 @@ NifSkope::NifSkope() tree->setItemDelegate( nif->createDelegate( this, book ) ); tree->installEventFilter( this ); tree->header()->moveSection( 1, 2 ); - tree->header()->resizeSection( NifModel::NameCol, 140 ); + tree->header()->resizeSection( NifModel::NameCol, 135 ); tree->header()->resizeSection( NifModel::ValueCol, 250 ); // Allow multi-row paste // Note: this has some side effects such as vertex selection @@ -214,6 +214,8 @@ NifSkope::NifSkope() header->setItemDelegate( nif->createDelegate( this, book ) ); header->installEventFilter( this ); header->header()->moveSection( 1, 2 ); + header->header()->resizeSection( NifModel::NameCol, 135 ); + header->header()->resizeSection( NifModel::ValueCol, 250 ); // KFM kfmtree = ui->kfmtree; diff --git a/src/nifskope_ui.cpp b/src/nifskope_ui.cpp index 403cabe6d..32541a11a 100644 --- a/src/nifskope_ui.cpp +++ b/src/nifskope_ui.cpp @@ -626,6 +626,7 @@ void NifSkope::initToolBars() tLOD->addWidget( lodSlider ); tLOD->setEnabled( false ); + tLOD->setVisible( false ); connect( lodSlider, &QSlider::valueChanged, ogl->getScene(), &Scene::updateLodLevel ); connect( lodSlider, &QSlider::valueChanged, ogl, &GLView::updateGL ); @@ -778,6 +779,8 @@ void NifSkope::onLoadComplete( bool success, QString & fname ) // Reconnect the models to the views swapModels(); + // Set List vs Tree + setListMode(); // Re-enable window ogl->setUpdatesEnabled( true ); @@ -792,6 +795,9 @@ void NifSkope::onLoadComplete( bool success, QString & fname ) select( nif->getHeader() ); header->setRootIndex( nif->getHeader() ); + // Refresh the header rows + header->updateConditions( nif->getHeader().child( 0, 0 ), nif->getHeader().child( 20, 0 ) ); + ogl->setOrientation( GLView::ViewFront ); enableUi(); diff --git a/src/ui/nifskope.ui b/src/ui/nifskope.ui index f9c5d4d7f..875156677 100644 --- a/src/ui/nifskope.ui +++ b/src/ui/nifskope.ui @@ -7,101 +7,10 @@ 0 0 1280 - 960 + 800
- - - false - - - - - - 0 - 0 - - - - - 0 - 24 - - - - - 16777215 - 24 - - - - false - - - #statusbar { -padding: 0 0 0 0; -margin: 0 0 0 0; -border: 0px solid transparent; -color: white; -background: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 rgba(33, 33, 33, 128), stop:0.039823 rgba(42, 42, 42, 128), stop:0.103245 rgba(50, 50, 50, 128), stop:0.171091 rgba(53, 53, 53, 128)); -} - -#statusbar QProgressBar { -margin: 0px; -padding: 0px; -border: 0px solid transparent; -text-align: center; -color: white; -font: 12px normal "Segoe UI"; -background: transparent; -} - -QProgressBar::chunk { -background-color: #05B8CC; -width: 20px; -} - -QProgressBar::text { -padding-bottom: 2px; -margin-top: -2px; -} - -#statusbar QSizeGrip { -background: url(":/img/sizeGrip") no-repeat; -width: 10px; height: 10px; -} - -#statusbar::text { -padding: 0 0 10px 0; -margin: -3px 0 0 0; - -} - -#statusbar::item { -border: 0px solid transparent; -} -#statusbar #filepathStatusbarWidget QLabel { -/*color: #333333;*/ -color: white; -padding: 0 0 1px 0; -margin: -1px 0 0 0; -font: 12px normal "Segoe UI"; -} - -#statusbar QPushButton { -padding: 0px; border: none; margin: 0px; -} - -#statusbar #filepathStatusbarWidget QPushButton:pressed { -margin-top: 1px; margin-left: 1px; -background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0.5, stop:0.139963 rgba(255, 255, 255, 255), stop:1 rgba(255, 255, 255, 0)); -} - -#statusbar #filepathStatusbarWidget QPushButton:hover { -background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0.5, stop:0.139963 rgba(255, 255, 255, 255), stop:1 rgba(255, 255, 255, 0)); -} - - + @@ -182,9 +91,12 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 - + + + false + - View + Animation Qt::BottomToolBarArea|Qt::TopToolBarArea @@ -201,19 +113,13 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 false - - - - - - + + + - - - false - + - Animation + View Qt::BottomToolBarArea|Qt::TopToolBarArea @@ -230,9 +136,12 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 false - - - + + + + + + @@ -451,6 +360,93 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 + + + + 0 + 0 + + + + + 0 + 24 + + + + + 16777215 + 24 + + + + false + + + #statusbar { +padding: 0 0 0 0; +margin: 0 0 0 0; +border: 0px solid transparent; +color: white; +background: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 rgba(33, 33, 33, 128), stop:0.039823 rgba(42, 42, 42, 128), stop:0.103245 rgba(50, 50, 50, 128), stop:0.171091 rgba(53, 53, 53, 128)); +} + +#statusbar QProgressBar { +margin: 0px; +padding: 0px; +border: 0px solid transparent; +text-align: center; +color: white; +font: 12px normal "Segoe UI"; +background: transparent; +} + +QProgressBar::chunk { +background-color: #05B8CC; +width: 20px; +} + +QProgressBar::text { +padding-bottom: 2px; +margin-top: -2px; +} + +#statusbar QSizeGrip { +background: url(":/img/sizeGrip") no-repeat; +width: 10px; height: 10px; +} + +#statusbar::text { +padding: 0 0 10px 0; +margin: -3px 0 0 0; + +} + +#statusbar::item { +border: 0px solid transparent; +} +#statusbar #filepathStatusbarWidget QLabel { +/*color: #333333;*/ +color: white; +padding: 0 0 1px 0; +margin: -1px 0 0 0; +font: 12px normal "Segoe UI"; +} + +#statusbar QPushButton { +padding: 0px; border: none; margin: 0px; +} + +#statusbar #filepathStatusbarWidget QPushButton:pressed { +margin-top: 1px; margin-left: 1px; +background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0.5, stop:0.139963 rgba(255, 255, 255, 255), stop:1 rgba(255, 255, 255, 0)); +} + +#statusbar #filepathStatusbarWidget QPushButton:hover { +background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0.5, stop:0.139963 rgba(255, 255, 255, 255), stop:1 rgba(255, 255, 255, 0)); +} + + true diff --git a/src/ui/widgets/nifview.h b/src/ui/widgets/nifview.h index 7e8f820c5..9e5e2abf0 100644 --- a/src/ui/widgets/nifview.h +++ b/src/ui/widgets/nifview.h @@ -78,9 +78,9 @@ public slots: //! Sets Hiding of non-applicable rows void setRowHiding( bool ); -protected slots: //! Updates version conditions (connect to dataChanged) void updateConditions( const QModelIndex & topLeft, const QModelIndex & bottomRight ); +protected slots: //! Recursively updates version conditions void updateConditionRecurse( const QModelIndex & index ); //! Called when the current index changes From da0d4d38ebf9b311e707c37c19e882c58deef427 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Fri, 15 Dec 2017 12:24:35 -0500 Subject: [PATCH 130/152] [UI] Fix keypress crash in Header tab The Header tab doesn't always have something selected by default like the other tree views, so a crash could happen if nothing is selected. --- src/ui/widgets/nifview.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ui/widgets/nifview.cpp b/src/ui/widgets/nifview.cpp index 5f3d75c54..fc2e3bfc0 100644 --- a/src/ui/widgets/nifview.cpp +++ b/src/ui/widgets/nifview.cpp @@ -350,6 +350,9 @@ void NifTreeView::keyPressEvent( QKeyEvent * e ) QModelIndexList selectedRows = selectionModel()->selectedIndexes(); QModelIndexList valueColumns = valueIndexList( selectedRows ); + if ( !(selectedRows.size() && valueColumns.size()) ) + return; + auto firstRow = selectedRows.at( 0 ); auto firstValue = valueColumns.at( 0 ); auto firstRowType = nif->getValue( firstRow ).type(); From 11f2980e3d5fd4b5c62e800abeccf5ef0c96b065 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Fri, 15 Dec 2017 20:17:39 -0500 Subject: [PATCH 131/152] [GL] NiMesh rewrite No. 3 There were a multitude of issues with my previous implementation, though not as severe as the initial code. It was adding triangles for each datastream among other things. Submeshes were also not supported. Currently an index can still not exceed USHRT_MAX so if the index in a submesh + the offset is allowed to exceed this, then additional changes will need to be made, such as actually creating Mesh child classes for each submesh. Skinning is also still not supported. --- src/data/niftypes.h | 37 ++++++ src/gl/glmesh.cpp | 292 ++++++++++++++++++++++++++++------------- src/model/nifmodel.cpp | 69 ++++++---- src/model/nifmodel.h | 6 + 4 files changed, 282 insertions(+), 122 deletions(-) diff --git a/src/data/niftypes.h b/src/data/niftypes.h index 9bd43ba28..f861b1259 100644 --- a/src/data/niftypes.h +++ b/src/data/niftypes.h @@ -1896,6 +1896,30 @@ typedef enum { PRIMITIVE_POINTS } PrimitiveType; +typedef enum { + CPU_READ = 0x01, + CPU_WRITE_STATIC = 0x02, + CPU_WRITE_MUTABLE = 0x04, + CPU_WRITE_VOLATILE = 0x08, + GPU_READ = 0x10, + GPU_WRITE = 0x20, + CPU_WRITE_STATIC_INITIALIZED = 0x40 +} DataStreamAccess; + +typedef enum { + USAGE_VERTEX_INDEX, + USAGE_VERTEX, + USAGE_SHADERCONSTANT, + USAGE_USER, + USAGE_DISPLAYLIST +} DataStreamUsage; + +struct DataStreamMetadata +{ + DataStreamUsage usage; + DataStreamAccess access; +}; + typedef enum { F_UNKNOWN = 0x00000000, F_INT8_1 = 0x00010101, @@ -2011,6 +2035,19 @@ typedef enum E_DISPLAYLIST = 29 } Semantic; +typedef enum { + HAS_NONE = 0, + HAS_POSITION = 1 << E_POSITION, + HAS_NORMAL = 1 << E_NORMAL, + HAS_BINORMAL = 1 << E_BINORMAL, + HAS_TANGENT = 1 << E_TANGENT, + HAS_TEXCOORD = 1 << E_TEXCOORD, + HAS_BLENDWEIGHT = 1 << E_BLENDWEIGHT, + HAS_BLENDINDICES = 1 << E_BLENDINDICES, + HAS_COLOR = 1 << E_COLOR, + HAS_INDEX = 1 << E_INDEX +} SemanticFlags; + #define SEM(string) {#string, E_##string}, const QMap semanticStrings = { diff --git a/src/gl/glmesh.cpp b/src/gl/glmesh.cpp index f345703eb..f2f0af35d 100644 --- a/src/gl/glmesh.cpp +++ b/src/gl/glmesh.cpp @@ -48,6 +48,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //! @file glmesh.cpp Scene management for visible meshes such as NiTriShapes. +const char * NIMESH_ABORT = QT_TR_NOOP( "NiMesh rendering encountered unsupported types. Rendering may be broken." ); + Shape::Shape( Scene * s, const QModelIndex & b ) : Node( s, b ) { @@ -220,10 +222,11 @@ void Mesh::update( const NifModel * nif, const QModelIndex & index ) if ( nif->checkVersion( 0x14050000, 0 ) && nif->inherits( iBlock, "NiMesh" ) ) { qDebug() << nif->get( iBlock, "Num Submeshes" ) << " submeshes"; - iData = nif->getIndex( iBlock, "Datas" ); + iData = nif->getIndex( iBlock, "Datastreams" ); if ( iData.isValid() ) { qDebug() << "Got " << nif->rowCount( iData ) << " rows of data"; updateData = true; + updateBounds = true; } else { qDebug() << "Did not find data in NiMesh ???"; } @@ -315,88 +318,153 @@ void Mesh::transform() weights.clear(); triangles.clear(); - QString abortMsg = "NiMesh rendering encountered unsupported types. Rendering may be broken."; + - auto meshPrimitiveType = nif->get( iBlock, "Primitive Type" ); + // All the semantics used by this mesh + NiMesh::SemanticFlags semFlags = NiMesh::HAS_NONE; + // Loop over the data once for initial setup and validation + // and build the semantic-index maps for each datastream's components + using CompSemIdxMap = QVector>; + QVector compSemanticIndexMaps; for ( int i = 0; i < nif->rowCount( iData ); i++ ) { - // each data reference is to a single data stream - quint32 stream = nif->getLink( iData.child( i, 0 ), "Stream" ); - qDebug() << "Data stream: " << stream; - // can have multiple submeshes, unsure of exact meaning - ushort numSubmeshes = nif->get( iData.child( i, 0 ), "Num Submeshes" ); - qDebug() << "Submeshes: " << numSubmeshes; - QPersistentModelIndex submeshMap = nif->getIndex( iData.child( i, 0 ), "Submesh To Region Map" ); + auto stream = nif->getLink( iData.child( i, 0 ), "Stream" ); + auto iDataStream = nif->getBlock( stream ); - for ( int j = 0; j < numSubmeshes; j++ ) { - qDebug() << "Submesh" << j << "maps to region" << nif->get( submeshMap.child( j, 0 ) ); - } + auto usage = NiMesh::DataStreamUsage( nif->get( iDataStream, "Usage" ) ); + auto access = nif->get( iDataStream, "Access" ); - // each stream can have multiple components, and each has a starting index - QMap> componentIndexMap; - int numComponents = nif->get( iData.child( i, 0 ), "Num Components" ); - qDebug() << "Components: " << numComponents; - // semantics determine the usage - QPersistentModelIndex componentSemantics = nif->getIndex( iData.child( i, 0 ), "Component Semantics" ); + // Invalid Usage and Access, abort + if ( usage == access && access == 0 ) + return; + // For each datastream, store the semantic and the index (used for E_TEXCOORD) + auto iComponentSemantics = nif->getIndex( iData.child( i, 0 ), "Component Semantics" ); + uint numComponents = nif->get( iData.child( i, 0 ), "Num Components" ); + CompSemIdxMap compSemanticIndexMap; for ( uint j = 0; j < numComponents; j++ ) { - auto name = NiMesh::semanticStrings.value(nif->get( componentSemantics.child( j, 0 ), "Name" )); - uint index = nif->get( componentSemantics.child( j, 0 ), "Index" ); - qDebug() << "Component" << name << "at position" << index << "of component" << j << "in stream" << stream; - componentIndexMap.insert( j, {name, index} ); - } + auto name = nif->get( iComponentSemantics.child( j, 0 ), "Name" ); + auto sem = NiMesh::semanticStrings.value( name ); + uint idx = nif->get( iComponentSemantics.child( j, 0 ), "Index" ); + compSemanticIndexMap.insert( j, {sem, idx} ); - // now the data stream itself... - QPersistentModelIndex dataStream = nif->getBlock( stream ); - QByteArray streamData = nif->get( nif->getIndex( dataStream, "Data" ).child( 0, 0 ) ); - QBuffer streamBuffer( &streamData ); - streamBuffer.open( QIODevice::ReadOnly ); + // Create UV stubs for multi-coord systems + if ( sem == NiMesh::E_TEXCOORD ) + coords.append( QVector() ); - // each region exists within the data stream at the specified index - quint32 numRegions = nif->get( dataStream, "Num Regions" ); - QPersistentModelIndex regions = nif->getIndex( dataStream, "Regions" ); - quint32 totalIndices = 0; + // Assure Index datastream is first and Usage is correct + bool invalidIndex = false; + if ( (sem == NiMesh::E_INDEX && (i != 0 || usage != NiMesh::USAGE_VERTEX_INDEX)) + || (usage == NiMesh::USAGE_VERTEX_INDEX && (i != 0 || sem != NiMesh::E_INDEX)) ) + invalidIndex = true; + + if ( invalidIndex ) { + Message::append( tr( NIMESH_ABORT ), + tr( "[%1] NifSkope requires 'INDEX' datastream be first, with Usage type 'USAGE_VERTEX_INDEX'." ) + .arg( stream ), + QMessageBox::Warning ); + return; + } - if ( regions.isValid() ) - for ( quint32 j = 0; j < numRegions; j++ ) - totalIndices += nif->get( regions.child( j, 0 ), "Num Indices" ); + semFlags = NiMesh::SemanticFlags(semFlags | (1 << sem)); + } + compSemanticIndexMaps << compSemanticIndexMap; + } - // stream components are interleaved, so we need to know their type before we read them - QVector typeList; + // This NiMesh does not have vertices, abort + if ( !(semFlags & NiMesh::HAS_POSITION) ) + return; - uint numStreamComponents = nif->get( dataStream, "Num Components" ); - for ( uint j = 0; j < numStreamComponents; j++ ) { - auto format = nif->get( nif->getIndex( dataStream, "Component Formats" ).child( j, 0 ) ); - typeList.append( format ); + // The number of triangle indices across the submeshes for this NiMesh + quint32 totalIndices = 0; + // The highest triangle index value in this NiMesh + quint32 maxIndex = 0; + // The Nth component after ignoring DataStreamUsage > 1 + int compIdx = 0; + for ( int i = 0; i < nif->rowCount( iData ); i++ ) { + // TODO: For now, submeshes are not actually used and the regions are + // filled in order for each data stream. + // Submeshes may be required if total index values exceed USHRT_MAX + QMap submeshMap; + ushort numSubmeshes = nif->get( iData.child( i, 0 ), "Num Submeshes" ); + auto iSubmeshMap = nif->getIndex( iData.child( i, 0 ), "Submesh To Region Map" ); + for ( ushort j = 0; j < numSubmeshes; j++ ) + submeshMap.insert( j, nif->get( iSubmeshMap.child( j, 0 ) ) ); + + // Get the datastream + quint32 stream = nif->getLink( iData.child( i, 0 ), "Stream" ); + auto iDataStream = nif->getBlock( stream ); + + auto usage = NiMesh::DataStreamUsage(nif->get( iDataStream, "Usage" )); + // Only process USAGE_VERTEX and USAGE_VERTEX_INDEX + if ( usage > NiMesh::USAGE_VERTEX ) + continue; + + // Datastream can be split into multiple regions + // Each region has a Start Index which is added as an offset to the index read from the stream + QVector> regions; + quint32 numRegions = nif->get( iDataStream, "Num Regions" ); + quint32 numIndices = 0; + auto iRegions = nif->getIndex( iDataStream, "Regions" ); + if ( iRegions.isValid() ) { + for ( quint32 j = 0; j < numRegions; j++ ) { + regions.append( { nif->get( iRegions.child( j, 0 ), "Start Index" ), + nif->get( iRegions.child( j, 0 ), "Num Indices" ) } + ); + + numIndices += regions[j].second; + } + } - auto component = componentIndexMap.value( j ); - auto compType = component.first; - auto startIndex = component.second; + if ( usage == NiMesh::USAGE_VERTEX_INDEX ) { + totalIndices = numIndices; + // RESERVE not RESIZE + indices.reserve( totalIndices ); + } else if ( compIdx == 1 ) { + // Indices should be built already + if ( indices.size() != totalIndices ) + return; + + quint32 maxSize = maxIndex + 1; + // RESIZE + verts.resize( maxSize ); + norms.resize( maxSize ); + tangents.resize( maxSize ); + bitangents.resize( maxSize ); + colors.resize( maxSize ); + weights.resize( maxSize ); + if ( coords.size() == 0 ) + coords.resize( 1 ); + + for ( auto & c : coords ) + c.resize( maxSize ); + } - // Create UV stubs for multi-coord systems - if ( compType == NiMesh::E_TEXCOORD ) - coords.append( QVector() ); + // Get the format of each component + QVector datastreamFormats; + uint numStreamComponents = nif->get( iDataStream, "Num Components" ); + for ( uint j = 0; j < numStreamComponents; j++ ) { + auto format = nif->get( nif->getIndex( iDataStream, "Component Formats" ).child( j, 0 ) ); + datastreamFormats.append( NiMesh::DataStreamFormat(format) ); } - verts.reserve( totalIndices ); - norms.reserve( totalIndices ); - tangents.reserve( totalIndices ); - bitangents.reserve( totalIndices ); - colors.reserve( totalIndices ); - indices.reserve( totalIndices ); - weights.reserve( totalIndices ); - for ( auto & c : coords ) - c.reserve( totalIndices ); + Q_ASSERT( compSemanticIndexMaps[i].size() == numStreamComponents ); auto tempMdl = std::make_unique( this ); + QByteArray streamData = nif->get( nif->getIndex( iDataStream, "Data" ).child( 0, 0 ) ); + QBuffer streamBuffer( &streamData ); + streamBuffer.open( QIODevice::ReadOnly ); + NifIStream tempInput( tempMdl.get(), &streamBuffer ); NifValue tempValue; bool abort = false; - for ( uint j = 0; j < totalIndices; j++ ) { + for ( const auto & r : regions ) for ( uint j = 0; j < r.second; j++ ) { + auto off = r.first; + Q_ASSERT( totalIndices >= off + j ); for ( uint k = 0; k < numStreamComponents; k++ ) { - auto typeK = typeList[k]; + auto typeK = datastreamFormats[k]; int typeLength = ( (typeK & 0x000F0000) >> 0x10 ); int typeSize = ( (typeK & 0x00000F00) >> 0x08 ); @@ -448,61 +516,83 @@ void Mesh::transform() for ( int l = 0; l < typeLength; l++ ) { tempInput.read( tempValue ); - qDebug() << tempValue.toString(); } - auto compType = componentIndexMap.value( k ).first; + auto compType = compSemanticIndexMaps[i].value( k ).first; switch ( typeK ) { case NiMesh::F_FLOAT32_3: case NiMesh::F_FLOAT16_3: + Q_ASSERT( usage == NiMesh::USAGE_VERTEX ); switch ( compType ) { case NiMesh::E_POSITION: case NiMesh::E_POSITION_BP: - verts.append( tempValue.get() ); + verts[j + off] = tempValue.get(); break; case NiMesh::E_NORMAL: case NiMesh::E_NORMAL_BP: - norms.append( tempValue.get() ); + norms[j + off] = tempValue.get(); break; case NiMesh::E_TANGENT: case NiMesh::E_TANGENT_BP: - tangents.append( tempValue.get() ); + tangents[j + off] = tempValue.get(); break; case NiMesh::E_BINORMAL: case NiMesh::E_BINORMAL_BP: - bitangents.append( tempValue.get() ); + bitangents[j + off] = tempValue.get(); break; default: break; } break; case NiMesh::F_UINT16_1: - if ( compType == NiMesh::E_INDEX ) - indices.append( tempValue.get() ); + if ( compType == NiMesh::E_INDEX ) { + Q_ASSERT( usage == NiMesh::USAGE_VERTEX_INDEX ); + // TODO: The total index value across all submeshes + // is likely allowed to exceed USHRT_MAX. + // For now limit the index. + quint32 ind = tempValue.get() + off; + if ( ind > 0xFFFF ) + qDebug() << QString( "[%1] %2" ).arg( stream ).arg( ind ); + + ind = std::min( ind, (quint32)0xFFFF ); + + // Store the highest index + if ( ind > maxIndex ) + maxIndex = ind; + + indices.append( (quint16)ind ); + } break; case NiMesh::F_FLOAT32_2: case NiMesh::F_FLOAT16_2: + Q_ASSERT( usage == NiMesh::USAGE_VERTEX ); if ( compType == NiMesh::E_TEXCOORD ) { - quint32 coordSet = componentIndexMap.value( k ).second; - coords[coordSet].append( tempValue.get() ); + quint32 coordSet = compSemanticIndexMaps[i].value( k ).second; + Q_ASSERT( coords.size() > coordSet ); + coords[coordSet][j + off] = tempValue.get(); } break; + case NiMesh::F_UINT8_4: + // BLENDINDICES, do nothing for now + break; case NiMesh::F_NORMUINT8_4: + Q_ASSERT( usage == NiMesh::USAGE_VERTEX ); if ( compType == NiMesh::E_COLOR ) - colors.append( tempValue.get() ); + colors[j + off] = tempValue.get(); break; case NiMesh::F_NORMUINT8_4_BGRA: + Q_ASSERT( usage == NiMesh::USAGE_VERTEX ); if ( compType == NiMesh::E_COLOR ) { // Swizzle BGRA -> RGBA auto c = tempValue.get().data(); - colors.append( {c[2], c[1], c[0], c[3]} ); + colors[j + off] = {c[2], c[1], c[0], c[3]}; } break; default: - Message::append( abortMsg, QString( "[%1] Unsupported Component: %2" ).arg( stream ) + Message::append( tr( NIMESH_ABORT ), tr( "[%1] Unsupported Component: %2" ).arg( stream ) .arg( NifValue::enumOptionName( "ComponentFormat", typeK ) ), - QMessageBox::Critical ); + QMessageBox::Warning ); abort = true; break; } @@ -515,26 +605,42 @@ void Mesh::transform() // Clear is extremely expensive. Must be outside of loop tempMdl->clear(); - // Make geometry - triangles.reserve( indices.size() / 3 ); - switch ( meshPrimitiveType ) - { - case NiMesh::PRIMITIVE_TRIANGLES: - for ( int k = 0; k < indices.size(); k += 3 ) - triangles.append( { indices[k], indices[k + 1], indices[k + 2] } ); - break; - case NiMesh::PRIMITIVE_TRISTRIPS: - case NiMesh::PRIMITIVE_LINES: - case NiMesh::PRIMITIVE_LINESTRIPS: - case NiMesh::PRIMITIVE_QUADS: - case NiMesh::PRIMITIVE_POINTS: - Message::append( abortMsg, - QString( "[%1] Unsupported Primitive: %2" ) - .arg( nif->getBlockNumber( iBlock ) ) - .arg( NifValue::enumOptionName( "MeshPrimitiveType", meshPrimitiveType ) ), - QMessageBox::Critical ); - break; - } + compIdx++; + } + + // Clear unused vertex attributes + // Note: Do not clear normals as this breaks fixed function for some reason + if ( !(semFlags & NiMesh::HAS_BINORMAL) ) + bitangents.clear(); + if ( !(semFlags & NiMesh::HAS_TANGENT) ) + tangents.clear(); + if ( !(semFlags & NiMesh::HAS_COLOR) ) + colors.clear(); + if ( !(semFlags & NiMesh::HAS_BLENDINDICES) || !(semFlags & NiMesh::HAS_BLENDWEIGHT) ) + weights.clear(); + + Q_ASSERT( verts.size() == maxIndex + 1 ); + Q_ASSERT( indices.size() == totalIndices ); + + // Make geometry + triangles.resize( indices.size() / 3 ); + auto meshPrimitiveType = nif->get( iBlock, "Primitive Type" ); + switch ( meshPrimitiveType ) { + case NiMesh::PRIMITIVE_TRIANGLES: + for ( int k = 0, t = 0; k < indices.size(); k += 3, t++ ) + triangles[t] = { indices[k], indices[k + 1], indices[k + 2] }; + break; + case NiMesh::PRIMITIVE_TRISTRIPS: + case NiMesh::PRIMITIVE_LINES: + case NiMesh::PRIMITIVE_LINESTRIPS: + case NiMesh::PRIMITIVE_QUADS: + case NiMesh::PRIMITIVE_POINTS: + Message::append( tr( NIMESH_ABORT ), tr( "[%1] Unsupported Primitive: %2" ) + .arg( nif->getBlockNumber( iBlock ) ) + .arg( NifValue::enumOptionName( "MeshPrimitiveType", meshPrimitiveType ) ), + QMessageBox::Warning + ); + break; } } else { diff --git a/src/model/nifmodel.cpp b/src/model/nifmodel.cpp index 44246eefc..b011a0cc1 100644 --- a/src/model/nifmodel.cpp +++ b/src/model/nifmodel.cpp @@ -288,6 +288,40 @@ void NifModel::updateFooter() * header functions */ +QString NifModel::extractRTTIArgs( const QString & RTTIName, NiMesh::DataStreamMetadata & metadata ) const +{ + QStringList nameAndArgs = RTTIName.split( "\x01" ); + Q_ASSERT( nameAndArgs.size() >= 1 ); + + if ( nameAndArgs[0] == QLatin1String( "NiDataStream" ) ) { + Q_ASSERT( nameAndArgs.size() == 3 ); + metadata.usage = NiMesh::DataStreamUsage( nameAndArgs[1].toInt() ); + metadata.access = NiMesh::DataStreamAccess( nameAndArgs[2].toInt() ); + } + + return nameAndArgs[0]; +} + +QString NifModel::createRTTIName( const QModelIndex & iBlock ) const +{ + return createRTTIName( static_cast(iBlock.internalPointer()) ); +} + +QString NifModel::createRTTIName( const NifItem * block ) const +{ + if ( !block ) + return {}; + + QString blockName = block->name(); + if ( blockName == QLatin1String( "NiDataStream" ) ) { + blockName = QString( "NiDataStream\x01%1\x01%2" ) + .arg( block->child( "Usage" )->value().get() ) + .arg( block->child( "Access" )->value().get() ); + } + + return blockName; +} + QModelIndex NifModel::getHeader() const { QModelIndex header = index( 0, 0 ); @@ -327,13 +361,7 @@ void NifModel::updateHeader() for ( int r = 1; r < root->childCount() - 1; r++ ) { NifItem * block = root->child( r ); - - // NiMesh hack - QString blockName = block->name(); - if ( blockName == "NiDataStream" ) { - blockName = QString( "NiDataStream\x01%1\x01%2" ).arg( block->child( "Usage" )->value().get() ).arg( block->child( "Access" )->value().get() ); - qDebug() << "Changing blockname to " << blockName; - } + QString blockName = createRTTIName( block ); int bTypeIdx = blocktypes.indexOf( blockName ); if ( bTypeIdx < 0 ) { @@ -1838,27 +1866,10 @@ bool NifModel::load( QIODevice & device ) } // Hack for NiMesh data streams - qint32 dataStreamUsage = -1; - qint32 dataStreamAccess = -1; - - if ( blktyp.startsWith( "NiDataStream\x01" ) ) { - QStringList splitStream = blktyp.split( "\x01" ); - blktyp = splitStream[0]; - bool ok; - dataStreamUsage = splitStream[1].toInt( &ok ); + NiMesh::DataStreamMetadata metadata = {}; - if ( !ok ) { - throw tr( "Unknown NiDataStream" ); - } - - dataStreamAccess = splitStream[2].toInt( &ok ); - - if ( !ok ) { - throw tr( "Unknown NiDataStream" ); - } - - qDebug() << "Loaded NiDataStream with usage " << dataStreamUsage << " access " << dataStreamAccess; - } + if ( blktyp.startsWith( "NiDataStream\x01" ) ) + blktyp = extractRTTIArgs( blktyp, metadata ); if ( isNiBlock( blktyp ) ) { //qDebug() << "loading block" << c << ":" << blktyp ); @@ -1871,8 +1882,8 @@ bool NifModel::load( QIODevice & device ) // NiMesh hack if ( blktyp == "NiDataStream" ) { - set( newBlock, "Usage", dataStreamUsage ); - set( newBlock, "Access", dataStreamAccess ); + set( newBlock, "Usage", metadata.usage ); + set( newBlock, "Access", metadata.access ); } } else { auto m = tr( "warning: block %1 (%2) not inserted!" ).arg( c ).arg( blktyp ); diff --git a/src/model/nifmodel.h b/src/model/nifmodel.h index 71bd95ded..44370f6d8 100644 --- a/src/model/nifmodel.h +++ b/src/model/nifmodel.h @@ -147,6 +147,12 @@ class NifModel final : public BaseModel QModelIndex getHeader() const; //! Updates the header infos ( num blocks etc. ) void updateHeader(); + //! Extracts the 0x01 separated args from NiDataStream. NiDataStream is the only known block to use RTTI args. + QString extractRTTIArgs( const QString & RTTIName, NiMesh::DataStreamMetadata & metadata ) const; + //! Creates the 0x01 separated args for NiDataStream. NiDataStream is the only known block to use RTTI args. + QString createRTTIName( const QModelIndex & iBlock ) const; + //! Creates the 0x01 separated args for NiDataStream. NiDataStream is the only known block to use RTTI args. + QString createRTTIName( const NifItem * block ) const; //! Returns the model index of the NiFooter QModelIndex getFooter() const; From 1db175d7a4b44588712e6429fd7a5dc4f2a2b990 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Fri, 15 Dec 2017 20:30:51 -0500 Subject: [PATCH 132/152] [GL] Stencil and Material property updates Actually pass in all the stencil information to GL even though I can't find where it's ever used. Clamp shininess before sending to OpenGL. Needs investigation as to whether shininess needs to be normalized to the 0-128 range for GL. --- src/gl/glproperty.cpp | 86 ++++++++++++++++++++++++++----------------- src/gl/glproperty.h | 54 ++++++++++++++++++++++++++- 2 files changed, 105 insertions(+), 35 deletions(-) diff --git a/src/gl/glproperty.cpp b/src/gl/glproperty.cpp index 5763d5502..bf45710bb 100644 --- a/src/gl/glproperty.cpp +++ b/src/gl/glproperty.cpp @@ -636,7 +636,8 @@ void MaterialProperty::update( const NifModel * nif, const QModelIndex & index ) specular = Color4( nif->get( index, "Specular Color" ) ); emissive = Color4( nif->get( index, "Emissive Color" ) ); - shininess = nif->get( index, "Glossiness" ); + // OpenGL needs shininess clamped otherwise it generates GL_INVALID_VALUE + shininess = std::min( std::max( nif->get( index, "Glossiness" ), 0.0f ), 128.0f ); } } @@ -770,46 +771,54 @@ void glProperty( VertexColorProperty * p, bool vertexcolors ) void StencilProperty::update( const NifModel * nif, const QModelIndex & block ) { + using namespace Stencil; Property::update( nif, block ); if ( iBlock.isValid() && iBlock == block ) { - //static const GLenum functions[8] = { GL_NEVER, GL_LESS, GL_EQUAL, GL_LEQUAL, GL_GREATER, GL_NOTEQUAL, GL_GEQUAL, GL_ALWAYS }; - //static const GLenum operations[8] = { GL_KEEP, GL_ZERO, GL_REPLACE, GL_INCR, GL_DECR, GL_INVERT, GL_KEEP, GL_KEEP }; + static const GLenum funcMap[8] = { + GL_NEVER, GL_GEQUAL, GL_NOTEQUAL, GL_GREATER, GL_LEQUAL, GL_EQUAL, GL_LESS, GL_ALWAYS + }; + + static const GLenum opMap[6] = { + GL_KEEP, GL_ZERO, GL_REPLACE, GL_INCR, GL_DECR, GL_INVERT + }; - // ! glFrontFace( GL_CCW ) + int drawMode = 0; if ( nif->checkVersion( 0, 0x14000005 ) ) { - switch ( nif->get( iBlock, "Draw Mode" ) ) { - case 2: - cullEnable = true; - cullMode = GL_FRONT; - break; - case 3: - cullEnable = false; - cullMode = GL_BACK; - break; - case 1: - default: - cullEnable = true; - cullMode = GL_BACK; - break; - } + drawMode = nif->get( iBlock, "Draw Mode" ); + func = funcMap[std::max(nif->get( iBlock, "Stencil Function" ), (quint32)TEST_MAX - 1 )]; + failop = opMap[std::max( nif->get( iBlock, "Fail Action" ), (quint32)ACTION_MAX - 1 )]; + zfailop = opMap[std::max( nif->get( iBlock, "Z Fail Action" ), (quint32)ACTION_MAX - 1 )]; + zpassop = opMap[std::max( nif->get( iBlock, "Pass Action" ), (quint32)ACTION_MAX - 1 )]; + stencil = (nif->get( iBlock, "Stencil Enabled" ) & ENABLE_MASK); } else { - switch ( ( nif->get( iBlock, "Flags" ) >> 10 ) & 3 ) { - case 2: - cullEnable = true; - cullMode = GL_FRONT; - break; - case 3: - cullEnable = false; - cullMode = GL_BACK; - break; - case 1: - default: - cullEnable = true; - cullMode = GL_BACK; - break; - } + auto flags = nif->get( iBlock, "Flags" ); + drawMode = (flags & DRAW_MASK) >> DRAW_POS; + func = funcMap[(flags & TEST_MASK) >> TEST_POS]; + failop = opMap[(flags & FAIL_MASK) >> FAIL_POS]; + zfailop = opMap[(flags & ZFAIL_MASK) >> ZFAIL_POS]; + zpassop = opMap[(flags & ZPASS_MASK) >> ZPASS_POS]; + stencil = (flags & ENABLE_MASK); + } + + switch ( drawMode ) { + case DRAW_CW: + cullEnable = true; + cullMode = GL_FRONT; + break; + case DRAW_BOTH: + cullEnable = false; + cullMode = GL_BACK; + break; + case DRAW_CCW: + default: + cullEnable = true; + cullMode = GL_BACK; + break; } + + ref = nif->get( iBlock, "Stencil Ref" ); + mask = nif->get( iBlock, "Stencil Mask" ); } } @@ -822,9 +831,18 @@ void glProperty( StencilProperty * p ) glDisable( GL_CULL_FACE ); glCullFace( p->cullMode ); + + if ( p->stencil ) { + glEnable( GL_STENCIL_TEST ); + glStencilFunc( p->func, p->ref, p->mask ); + glStencilOp( p->failop, p->zfailop, p->zpassop ); + } else { + glDisable( GL_STENCIL_TEST ); + } } else { glEnable( GL_CULL_FACE ); glCullFace( GL_BACK ); + glDisable( GL_STENCIL_TEST ); } } diff --git a/src/gl/glproperty.h b/src/gl/glproperty.h index 0ceea9e89..752c83f09 100644 --- a/src/gl/glproperty.h +++ b/src/gl/glproperty.h @@ -361,6 +361,42 @@ class VertexColorProperty final : public Property REGISTER_PROPERTY( VertexColorProperty, VertexColor ) +namespace Stencil +{ + enum TestFunc + { + TEST_NEVER, + TEST_LESS, + TEST_EQUAL, + TEST_LESSEQUAL, + TEST_GREATER, + TEST_NOTEQUAL, + TEST_GREATEREQUAL, + TEST_ALWAYS, + TEST_MAX + }; + + enum Action + { + ACTION_KEEP, + ACTION_ZERO, + ACTION_REPLACE, + ACTION_INCREMENT, + ACTION_DECREMENT, + ACTION_INVERT, + ACTION_MAX + }; + + enum DrawMode + { + DRAW_CCW_OR_BOTH, + DRAW_CCW, + DRAW_CW, + DRAW_BOTH, + DRAW_MAX + }; +} + //! A Property that specifies stencil testing class StencilProperty final : public Property { @@ -375,10 +411,26 @@ class StencilProperty final : public Property friend void glProperty( StencilProperty * ); protected: + enum + { + ENABLE_MASK = 0x0001, + FAIL_MASK = 0x000E, + FAIL_POS = 1, + ZFAIL_MASK = 0x0070, + ZFAIL_POS = 4, + ZPASS_MASK = 0x0380, + ZPASS_POS = 7, + DRAW_MASK = 0x0C00, + DRAW_POS = 10, + TEST_MASK = 0x7000, + TEST_POS = 12 + }; + bool stencil = false; GLenum func = 0; - GLuint mask = 0; + GLuint ref = 0; + GLuint mask = 0xffffffff; GLenum failop = 0; GLenum zfailop = 0; From aa6b3d9f89c159f007ef330f2413ac14448081c4 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Fri, 15 Dec 2017 22:19:29 -0500 Subject: [PATCH 133/152] nifxml 0.9 sync for BSPackedCombined vis --- src/gl/bsshape.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/gl/bsshape.cpp b/src/gl/bsshape.cpp index 2a732db5c..8a6a3227d 100644 --- a/src/gl/bsshape.cpp +++ b/src/gl/bsshape.cpp @@ -485,7 +485,7 @@ void BSShape::drawSelection() const // Set current block name and detect if extra data auto blockName = nif->getBlockName( blk ); - if ( blockName == "BSPackedCombinedSharedGeomDataExtra" ) + if ( blockName.startsWith( "BSPackedCombined" ) ) extraData = true; // Don't do anything if this block is not the current block @@ -567,11 +567,11 @@ void BSShape::drawSelection() const } } - if ( blockName == "BSPackedCombinedSharedGeomDataExtra" && pBlock == iBlock ) { + if ( blockName.startsWith( "BSPackedCombined" ) && pBlock == iBlock ) { QVector idxs; if ( n == "Bounding Sphere" ) { idxs += idx; - } else if ( n == "BSPackedCombinedSharedGeomDataExtra" ) { + } else if ( n.startsWith( "BSPackedCombined" ) ) { auto data = nif->getIndex( idx, "Object Data" ); int dataCt = nif->rowCount( data ); @@ -593,7 +593,7 @@ void BSShape::drawSelection() const return; } - Vector3 pTrans = nif->get( pBlock, "Translation" ); + Vector3 pTrans = nif->get( pBlock.child( 1, 0 ), "Translation" ); auto iBSphere = nif->getIndex( pBlock, "Bounding Sphere" ); Vector3 pbvC = nif->get( iBSphere.child( 0, 2 ) ); float pbvR = nif->get( iBSphere.child( 1, 2 ) ); @@ -606,9 +606,11 @@ void BSShape::drawSelection() const glPopMatrix(); for ( auto i : idxs ) { - Matrix mat = nif->get( i.parent(), "Rotation" ); - //auto trans = nif->get( idx.parent(), "Translation" ); - float scale = nif->get( i.parent(), "Scale" ); + // Transform compound + auto iTrans = i.parent().child( 1, 0 ); + Matrix mat = nif->get( iTrans, "Rotation" ); + //auto trans = nif->get( iTrans, "Translation" ); + float scale = nif->get( iTrans, "Scale" ); Vector3 bvC = nif->get( i, "Center" ); float bvR = nif->get( i, "Radius" ); From c0b44e2031db5ee29de3bc283bdd2554748210e0 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sat, 16 Dec 2017 07:37:41 -0500 Subject: [PATCH 134/152] [GL] Per-frame major allocation optimizations Declared all the basic NIF types for the Qt meta type system and switched all the data() pointer to constData() for the gl calls. --- src/data/niftypes.h | 15 +++++++ src/gl/bsshape.cpp | 18 ++++---- src/gl/glmesh.cpp | 106 ++++++++------------------------------------ src/gl/glmesh.h | 11 +++-- src/gl/renderer.cpp | 15 ++++--- 5 files changed, 59 insertions(+), 106 deletions(-) diff --git a/src/data/niftypes.h b/src/data/niftypes.h index f861b1259..6f13cb869 100644 --- a/src/data/niftypes.h +++ b/src/data/niftypes.h @@ -1884,6 +1884,21 @@ inline QDataStream & operator>>( QDataStream & ds, BSVertexDesc & d ) return ds; } +// Qt container optimizations + +Q_DECLARE_TYPEINFO( Vector2, Q_MOVABLE_TYPE ); +Q_DECLARE_TYPEINFO( Vector3, Q_MOVABLE_TYPE ); +Q_DECLARE_TYPEINFO( HalfVector3, Q_MOVABLE_TYPE ); +Q_DECLARE_TYPEINFO( ByteVector3, Q_MOVABLE_TYPE ); +Q_DECLARE_TYPEINFO( Vector4, Q_MOVABLE_TYPE ); +Q_DECLARE_TYPEINFO( Color3, Q_MOVABLE_TYPE ); +Q_DECLARE_TYPEINFO( Color4, Q_MOVABLE_TYPE ); +Q_DECLARE_TYPEINFO( ByteColor4, Q_MOVABLE_TYPE ); +Q_DECLARE_TYPEINFO( Triangle, Q_MOVABLE_TYPE ); +Q_DECLARE_TYPEINFO( Quat, Q_MOVABLE_TYPE ); +Q_DECLARE_TYPEINFO( Matrix, Q_MOVABLE_TYPE ); +Q_DECLARE_TYPEINFO( Transform, Q_MOVABLE_TYPE ); + namespace NiMesh { diff --git a/src/gl/bsshape.cpp b/src/gl/bsshape.cpp index 8a6a3227d..552a6b668 100644 --- a/src/gl/bsshape.cpp +++ b/src/gl/bsshape.cpp @@ -123,8 +123,8 @@ void BSShape::update( const NifModel * nif, const QModelIndex & index ) coords.clear(); colors.clear(); - // For compatibility with coords QList - QVector coordset; + // For compatibility with coords list + TexCoords coordset; for ( int i = 0; i < numVerts; i++ ) { auto idx = nif->index( i, 0, iVertData ); @@ -371,20 +371,20 @@ void BSShape::drawShapes( NodeList * secondPass, bool presort ) } glEnableClientState( GL_VERTEX_ARRAY ); - glVertexPointer( 3, GL_FLOAT, 0, transVerts.data() ); + glVertexPointer( 3, GL_FLOAT, 0, transVerts.constData() ); if ( !Node::SELECTING ) { glEnableClientState( GL_NORMAL_ARRAY ); - glNormalPointer( GL_FLOAT, 0, transNorms.data() ); + glNormalPointer( GL_FLOAT, 0, transNorms.constData() ); bool doVCs = (bssp && (bssp->getFlags2() & ShaderFlags::SLSF2_Vertex_Colors)); // Always do vertex colors for FO4 BSESP if ( nifVersion == 130 && bsesp && colors.count() ) doVCs = true; - Color4 * c = nullptr; + const Color4 * c = nullptr; if ( colors.count() && (scene->options & Scene::DoVertexColors) && doVCs ) { - c = colors.data(); + c = colors.constData(); } if ( c ) { @@ -401,11 +401,11 @@ void BSShape::drawShapes( NodeList * secondPass, bool presort ) if ( isDoubleSided ) { glCullFace( GL_FRONT ); - glDrawElements( GL_TRIANGLES, triangles.count() * 3, GL_UNSIGNED_SHORT, triangles.data() ); + glDrawElements( GL_TRIANGLES, triangles.count() * 3, GL_UNSIGNED_SHORT, triangles.constData() ); glCullFace( GL_BACK ); } - glDrawElements( GL_TRIANGLES, triangles.count() * 3, GL_UNSIGNED_SHORT, triangles.data() ); + glDrawElements( GL_TRIANGLES, triangles.count() * 3, GL_UNSIGNED_SHORT, triangles.constData() ); if ( !Node::SELECTING ) scene->renderer->stopProgram(); @@ -480,7 +480,7 @@ void BSShape::drawSelection() const bool extraData = false; auto nif = static_cast(idx.model()); - if ( !nif ) + if ( !nif || !blk.isValid() ) return; // Set current block name and detect if extra data diff --git a/src/gl/glmesh.cpp b/src/gl/glmesh.cpp index f2f0af35d..57e811d44 100644 --- a/src/gl/glmesh.cpp +++ b/src/gl/glmesh.cpp @@ -86,7 +86,6 @@ void Mesh::clear() transVerts.clear(); transNorms.clear(); transColors.clear(); - transColorsNoAlpha.clear(); transTangents.clear(); transBitangents.clear(); @@ -349,7 +348,7 @@ void Mesh::transform() // Create UV stubs for multi-coord systems if ( sem == NiMesh::E_TEXCOORD ) - coords.append( QVector() ); + coords.append( TexCoords() ); // Assure Index datastream is first and Usage is correct bool invalidIndex = false; @@ -677,7 +676,7 @@ void Mesh::transform() if ( uvcoord.isValid() ) { for ( int r = 0; r < nif->rowCount( uvcoord ); r++ ) { - QVector tc = nif->getArray( uvcoord.child( r, 0 ) ); + TexCoords tc = nif->getArray( uvcoord.child( r, 0 ) ); if ( tc.count() < verts.count() ) tc.clear(); @@ -942,53 +941,7 @@ void Mesh::transformShapes() transBitangents = bitangents; } - /* - //Commented this out because this appears from my tests to be an - //incorrect understanding of the flag we previously called "sort." - //Tests have shown that none of the games or official scene viewers - //ever sort triangles, regarless of the path. The triangles are always - //drawn in the order they exist in the triangle array. - - AlphaProperty * alphaprop = findProperty(); - - if ( alphaprop && alphaprop->sort() ) - { - - - - QVector< QPair< int, float > > triOrder( triangles.count() ); - if ( transformRigid ) - { - Transform vt = viewTrans(); - Vector3 ref = vt.rotation.inverted() * ( Vector3() - vt.translation ) / vt.scale; - int t = 0; - foreach ( Triangle tri, triangles ) - { - triOrder[t] = QPair( t, 0 - ( ref - verts.value( tri.v1() ) ).squaredLength() - ( ref - verts.value( tri.v2() ) ).squaredLength() - ( ref - verts.value( tri.v1() ) ).squaredLength() ); - t++; - } - } - else - { - int t = 0; - foreach ( Triangle tri, triangles ) - { - QPair< int, float > tp; - tp.first = t; - tp.second = transVerts.value( tri.v1() )[2] + transVerts.value( tri.v2() )[2] + transVerts.value( tri.v3() )[2]; - triOrder[t++] = tp; - } - } - qSort( triOrder.begin(), triOrder.end(), compareTriangles ); - sortedTriangles.resize( triangles.count() ); - for ( int t = 0; t < triangles.count(); t++ ) - sortedTriangles[t] = triangles[ triOrder[t].first ]; - } - else - { - */ sortedTriangles = triangles; - //} MaterialProperty * matprop = findProperty(); if ( matprop && matprop->alphaValue() != 1.0 ) { @@ -999,34 +952,13 @@ void Mesh::transformShapes() transColors[c] = colors[c].blend( a ); } else { transColors = colors; - } - - //if ( !bslsp ) - // bslsp = properties.get(); - - if ( bslsp ) { - transColorsNoAlpha.resize( colors.count() ); - if ( !(bslsp->getFlags1() & ShaderFlags::SLSF1_Vertex_Alpha) ) { - for ( int c = 0; c < colors.count(); c++ ) - transColorsNoAlpha[c] = Color4( transColors[c].red(), transColors[c].green(), transColors[c].blue(), 1.0f ); - } else { - transColorsNoAlpha.clear(); + if ( bslsp ) { + if ( !(bslsp->getFlags1() & ShaderFlags::SLSF1_Vertex_Alpha) ) { + for ( int c = 0; c < colors.count(); c++ ) + transColors[c] = Color4( colors[c].red(), colors[c].green(), colors[c].blue(), 1.0f ); + } } } - - //if ( !bsesp ) - // bsesp = properties.get(); - - //if ( bsesp ) { - // transColorsNoAlpha.resize( colors.count() ); - // if ( !(bsesp->getFlags1() & ShaderFlags::SLSF1_Vertex_Alpha) ) { - // //qDebug() << "No VA" << name; - // for ( int c = 0; c < colors.count(); c++ ) - // transColorsNoAlpha[c] = Color4( transColors[c].red(), transColors[c].green(), transColors[c].blue(), 1.0f ); - // } else { - // transColorsNoAlpha.clear(); - // } - //} } BoundSphere Mesh::bounds() const @@ -1097,12 +1029,12 @@ void Mesh::drawShapes( NodeList * secondPass, bool presort ) glPolygonOffset( 1.0f, 2.0f ); glEnableClientState( GL_VERTEX_ARRAY ); - glVertexPointer( 3, GL_FLOAT, 0, transVerts.data() ); + glVertexPointer( 3, GL_FLOAT, 0, transVerts.constData() ); if ( !Node::SELECTING ) { if ( transNorms.count() ) { glEnableClientState( GL_NORMAL_ARRAY ); - glNormalPointer( GL_FLOAT, 0, transNorms.data() ); + glNormalPointer( GL_FLOAT, 0, transNorms.constData() ); } // Do VCs if legacy or if either bslsp or bsesp is set @@ -1113,7 +1045,7 @@ void Mesh::drawShapes( NodeList * secondPass, bool presort ) && doVCs ) { glEnableClientState( GL_COLOR_ARRAY ); - glColorPointer( 4, GL_FLOAT, 0, (transColorsNoAlpha.count()) ? transColorsNoAlpha.data() : transColors.data() ); + glColorPointer( 4, GL_FLOAT, 0, transColors.constData() ); } else { if ( !hasVertexColors && (bslsp && bslsp->hasVertexColors) ) { // Correctly blacken the mesh if SLSF2_Vertex_Colors is still on @@ -1138,7 +1070,7 @@ void Mesh::drawShapes( NodeList * secondPass, bool presort ) // render the triangles if ( sortedTriangles.count() ) - glDrawElements( GL_TRIANGLES, sortedTriangles.count() * 3, GL_UNSIGNED_SHORT, sortedTriangles.data() ); + glDrawElements( GL_TRIANGLES, sortedTriangles.count() * 3, GL_UNSIGNED_SHORT, sortedTriangles.constData() ); } else { auto lod0 = nif->get( bsLOD, "Level 0 Size" ); @@ -1154,21 +1086,21 @@ void Mesh::drawShapes( NodeList * secondPass, bool presort ) switch ( scene->lodLevel ) { case Scene::Level2: if ( lod2tris.count() ) - glDrawElements( GL_TRIANGLES, lod2tris.count() * 3, GL_UNSIGNED_SHORT, lod2tris.data() ); + glDrawElements( GL_TRIANGLES, lod2tris.count() * 3, GL_UNSIGNED_SHORT, lod2tris.constData() ); case Scene::Level1: if ( lod1tris.count() ) - glDrawElements( GL_TRIANGLES, lod1tris.count() * 3, GL_UNSIGNED_SHORT, lod1tris.data() ); + glDrawElements( GL_TRIANGLES, lod1tris.count() * 3, GL_UNSIGNED_SHORT, lod1tris.constData() ); case Scene::Level0: default: if ( lod0tris.count() ) - glDrawElements( GL_TRIANGLES, lod0tris.count() * 3, GL_UNSIGNED_SHORT, lod0tris.data() ); + glDrawElements( GL_TRIANGLES, lod0tris.count() * 3, GL_UNSIGNED_SHORT, lod0tris.constData() ); break; } } // render the tristrips - for ( int s = 0; s < tristrips.count(); s++ ) - glDrawElements( GL_TRIANGLE_STRIP, tristrips[s].count(), GL_UNSIGNED_SHORT, tristrips[s].data() ); + for ( auto & s : tristrips ) + glDrawElements( GL_TRIANGLE_STRIP, s.count(), GL_UNSIGNED_SHORT, s.constData() ); if ( isDoubleSided ) { glEnable( GL_CULL_FACE ); @@ -1469,7 +1401,7 @@ void Mesh::drawSelection() const if ( n == "Faces" || n == "Strips" || n == "Strip Lengths" ) { glLineWidth( 1.5f ); - for ( const QVector& strip : tristrips ) { + for ( const TriStrip& strip : tristrips ) { quint16 a = strip.value( 0 ); quint16 b = strip.value( 1 ); @@ -1491,7 +1423,7 @@ void Mesh::drawSelection() const } if ( i >= 0 && !tristrips.isEmpty() ) { - QVector strip = tristrips[i]; + TriStrip strip = tristrips[i]; quint16 a = strip.value( 0 ); quint16 b = strip.value( 1 ); @@ -1534,7 +1466,7 @@ void Mesh::drawSelection() const glVertex( transVerts.value( vmap.value( tri.v1() ) ) ); glEnd(); } - for ( const QVector& strip : partitions[c].tristrips ) { + for ( const TriStrip& strip : partitions[c].tristrips ) { quint16 a = vmap.value( strip.value( 0 ) ); quint16 b = vmap.value( strip.value( 1 ) ); diff --git a/src/gl/glmesh.h b/src/gl/glmesh.h index 98268234c..d4e13c7c4 100644 --- a/src/gl/glmesh.h +++ b/src/gl/glmesh.h @@ -43,6 +43,11 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //! @file glmesh.h Mesh +using TriStrip = QVector; +Q_DECLARE_TYPEINFO( TriStrip, Q_MOVABLE_TYPE ); +using TexCoords = QVector; +Q_DECLARE_TYPEINFO( TexCoords, Q_MOVABLE_TYPE ); + class NifModel; class Shape : public Node @@ -95,11 +100,11 @@ class Shape : public Node //! Bitangents QVector bitangents; //! UV coordinate sets - QVector> coords; + QVector coords; //! Triangles QVector triangles; //! Strip points - QVector> tristrips; + QVector tristrips; //! Sorted triangles QVector sortedTriangles; //! Triangle indices @@ -113,8 +118,6 @@ class Shape : public Node QVector transNorms; //! Transformed colors (alpha blended) QVector transColors; - //! Transformed colors (alpha removed) - QVector transColorsNoAlpha; //! Transformed tangents QVector transTangents; //! Transformed bitangents diff --git a/src/gl/renderer.cpp b/src/gl/renderer.cpp index 015639141..fa1b2f713 100644 --- a/src/gl/renderer.cpp +++ b/src/gl/renderer.cpp @@ -955,10 +955,10 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & if ( itx.value() == "tangents" ) { if ( mesh->transTangents.count() ) { glEnableClientState( GL_TEXTURE_COORD_ARRAY ); - glTexCoordPointer( 3, GL_FLOAT, 0, mesh->transTangents.data() ); + glTexCoordPointer( 3, GL_FLOAT, 0, mesh->transTangents.constData() ); } else if ( mesh->tangents.count() ) { glEnableClientState( GL_TEXTURE_COORD_ARRAY ); - glTexCoordPointer( 3, GL_FLOAT, 0, mesh->tangents.data() ); + glTexCoordPointer( 3, GL_FLOAT, 0, mesh->tangents.constData() ); } else { return false; } @@ -966,10 +966,10 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & } else if ( itx.value() == "bitangents" ) { if ( mesh->transBitangents.count() ) { glEnableClientState( GL_TEXTURE_COORD_ARRAY ); - glTexCoordPointer( 3, GL_FLOAT, 0, mesh->transBitangents.data() ); + glTexCoordPointer( 3, GL_FLOAT, 0, mesh->transBitangents.constData() ); } else if ( mesh->bitangents.count() ) { glEnableClientState( GL_TEXTURE_COORD_ARRAY ); - glTexCoordPointer( 3, GL_FLOAT, 0, mesh->bitangents.data() ); + glTexCoordPointer( 3, GL_FLOAT, 0, mesh->bitangents.constData() ); } else { return false; } @@ -984,7 +984,7 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & return false; glEnableClientState( GL_TEXTURE_COORD_ARRAY ); - glTexCoordPointer( 2, GL_FLOAT, 0, mesh->coords[set].data() ); + glTexCoordPointer( 2, GL_FLOAT, 0, mesh->coords[set].constData() ); } else if ( bsprop ) { int txid = BSShaderLightingProperty::getId( itx.value() ); if ( txid < 0 ) @@ -996,7 +996,7 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & return false; glEnableClientState( GL_TEXTURE_COORD_ARRAY ); - glTexCoordPointer( 2, GL_FLOAT, 0, mesh->coords[set].data() ); + glTexCoordPointer( 2, GL_FLOAT, 0, mesh->coords[set].constData() ); } } @@ -1134,6 +1134,9 @@ void Renderer::setupFixedFunction( Shape * mesh, const PropertyList & props ) // setup texturing + if ( !(mesh->scene->options & Scene::DoTexturing) ) + return; + if ( TexturingProperty * texprop = props.get() ) { // standard multi texturing property int stage = 0; From 5639fce09844756233082a94fb838bcd89f03447 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sat, 16 Dec 2017 09:08:19 -0500 Subject: [PATCH 135/152] [UI] LOD slider improvements, FO4 support Reworked the implementation to be more generalized and on Shape instead of Mesh so that BSShape could use it. Added support for FO4. Fixed the UI behavior of the slider and the default position. --- src/gl/bsshape.cpp | 32 ++++++++++++++++++++++++++++ src/gl/glmesh.cpp | 26 ++++++++--------------- src/gl/glmesh.h | 4 ++-- src/nifskope_ui.cpp | 3 +++ src/ui/nifskope.ui | 52 ++++++++++++++++++++++----------------------- 5 files changed, 72 insertions(+), 45 deletions(-) diff --git a/src/gl/bsshape.cpp b/src/gl/bsshape.cpp index 552a6b668..28695694e 100644 --- a/src/gl/bsshape.cpp +++ b/src/gl/bsshape.cpp @@ -44,6 +44,10 @@ void BSShape::update( const NifModel * nif, const QModelIndex & index ) nifVersion = nif->getUserVersion2(); + isLOD = nif->isNiBlock( iBlock, "BSMeshLODTriShape" ); + if ( isLOD ) + emit nif->lodSliderChanged( true ); + auto vertexFlags = nif->get( iBlock, "Vertex Desc" ); bool isDynamic = nif->inherits( iBlock, "BSDynamicTriShape" ); @@ -338,6 +342,8 @@ void BSShape::drawShapes( NodeList * secondPass, bool presort ) if ( !(scene->options & Scene::ShowMarkers) && name.contains( "EditorMarker" ) ) return; + auto nif = static_cast(iBlock.model()); + if ( Node::SELECTING ) { if ( scene->selMode & Scene::SelObject ) { int s_nodeId = ID2COLORKEY( nodeId ); @@ -405,7 +411,33 @@ void BSShape::drawShapes( NodeList * secondPass, bool presort ) glCullFace( GL_BACK ); } + if ( !isLOD ) { glDrawElements( GL_TRIANGLES, triangles.count() * 3, GL_UNSIGNED_SHORT, triangles.constData() ); + } else if ( triangles.count() ) { + auto lod0 = nif->get( iBlock, "LOD0 Size" ); + auto lod1 = nif->get( iBlock, "LOD1 Size" ); + auto lod2 = nif->get( iBlock, "LOD2 Size" ); + + auto lod0tris = triangles.mid( 0, lod0 ); + auto lod1tris = triangles.mid( lod0, lod1 ); + auto lod2tris = triangles.mid( lod0 + lod1, lod2 ); + + // If Level2, render all + // If Level1, also render Level0 + switch ( scene->lodLevel ) { + case Scene::Level2: + if ( lod2tris.count() ) + glDrawElements( GL_TRIANGLES, lod2tris.count() * 3, GL_UNSIGNED_SHORT, lod2tris.constData() ); + case Scene::Level1: + if ( lod1tris.count() ) + glDrawElements( GL_TRIANGLES, lod1tris.count() * 3, GL_UNSIGNED_SHORT, lod1tris.constData() ); + case Scene::Level0: + default: + if ( lod0tris.count() ) + glDrawElements( GL_TRIANGLES, lod0tris.count() * 3, GL_UNSIGNED_SHORT, lod0tris.constData() ); + break; + } + } if ( !Node::SELECTING ) scene->renderer->stopProgram(); diff --git a/src/gl/glmesh.cpp b/src/gl/glmesh.cpp index 57e811d44..e7be9143b 100644 --- a/src/gl/glmesh.cpp +++ b/src/gl/glmesh.cpp @@ -60,7 +60,6 @@ Mesh::Mesh( Scene * s, const QModelIndex & b ) : Shape( s, b ) { } -bool Mesh::isBSLODPresent = false; void Mesh::clear() { @@ -89,7 +88,7 @@ void Mesh::clear() transTangents.clear(); transBitangents.clear(); - isBSLODPresent = false; + isLOD = false; isDoubleSided = false; } @@ -192,14 +191,9 @@ void Mesh::update( const NifModel * nif, const QModelIndex & index ) if ( (!iBlock.isValid() || !index.isValid()) && !updateSkin ) return; - if ( !isBSLODPresent ) { - - if ( nif->isNiBlock( iBlock, "BSLODTriShape" ) ) { - isBSLODPresent = true; - } - - emit nif->lodSliderChanged( isBSLODPresent ); - } + isLOD = nif->isNiBlock( iBlock, "BSLODTriShape" ); + if ( isLOD ) + emit nif->lodSliderChanged( true ); updateData |= ( iData == index ) || ( iTangentData == index ); updateSkin |= ( iSkin == index ); @@ -1065,17 +1059,15 @@ void Mesh::drawShapes( NodeList * secondPass, bool presort ) glDisable( GL_CULL_FACE ); } - auto bsLOD = nif->getBlock( iBlock, "BSLODTriShape" ); - if ( !bsLOD.isValid() ) { - + if ( !isLOD ) { // render the triangles if ( sortedTriangles.count() ) glDrawElements( GL_TRIANGLES, sortedTriangles.count() * 3, GL_UNSIGNED_SHORT, sortedTriangles.constData() ); - } else { - auto lod0 = nif->get( bsLOD, "Level 0 Size" ); - auto lod1 = nif->get( bsLOD, "Level 1 Size" ); - auto lod2 = nif->get( bsLOD, "Level 2 Size" ); + } else if ( sortedTriangles.count() ) { + auto lod0 = nif->get( iBlock, "LOD0 Size" ); + auto lod1 = nif->get( iBlock, "LOD1 Size" ); + auto lod2 = nif->get( iBlock, "LOD2 Size" ); auto lod0tris = sortedTriangles.mid( 0, lod0 ); auto lod1tris = sortedTriangles.mid( lod0, lod1 ); diff --git a/src/gl/glmesh.h b/src/gl/glmesh.h index d4e13c7c4..861a8943b 100644 --- a/src/gl/glmesh.h +++ b/src/gl/glmesh.h @@ -157,6 +157,8 @@ class Shape : public Node mutable BoundSphere boundSphere; mutable bool updateBounds = false; + + bool isLOD = false; }; //! A mesh @@ -198,8 +200,6 @@ class Mesh : public Shape //! Tangent data QPersistentModelIndex iTangentData; - - static bool isBSLODPresent; }; diff --git a/src/nifskope_ui.cpp b/src/nifskope_ui.cpp index 32541a11a..9f0b1ec05 100644 --- a/src/nifskope_ui.cpp +++ b/src/nifskope_ui.cpp @@ -758,6 +758,9 @@ void NifSkope::onLoadBegin() setEnabled( false ); ui->tAnim->setEnabled( false ); + ui->tLOD->setEnabled( false ); + ui->tLOD->setVisible( false ); + progress->setVisible( true ); progress->reset(); } diff --git a/src/ui/nifskope.ui b/src/ui/nifskope.ui index 875156677..d7f75f37c 100644 --- a/src/ui/nifskope.ui +++ b/src/ui/nifskope.ui @@ -117,32 +117,6 @@ - - - View - - - Qt::BottomToolBarArea|Qt::TopToolBarArea - - - - 36 - 36 - - - - TopToolBarArea - - - false - - - - - - - - @@ -182,6 +156,32 @@ + + + View + + + Qt::BottomToolBarArea|Qt::TopToolBarArea + + + + 36 + 36 + + + + TopToolBarArea + + + false + + + + + + + + From 3f5d8496ecafeafe3dcbb4ccd6abf104612bdc94 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sat, 16 Dec 2017 09:37:54 -0500 Subject: [PATCH 136/152] [UI] Add New Ref feature for "None" Ref links Adds a button to all None Refs that allows you to insert a new block of the proper type. Has filtering by type, version, and for controllers and interpolators limits it to the valid blocks for the parent or target type. Replaced the menu flyout creation for the other insert operations and improved the categorization and filtering on it using the new filtering methods. Also improved Bethesda NiExtraData auto-naming for new block insertions. --- res/icon/add.png | Bin 0 -> 1973 bytes res/nifskope.qrc | 1 + src/model/nifmodel.cpp | 10 + src/model/nifmodel.h | 2 + src/spells/blocks.cpp | 452 +++++++++++++++++++++++++++++++++++++---- 5 files changed, 427 insertions(+), 38 deletions(-) create mode 100644 res/icon/add.png diff --git a/res/icon/add.png b/res/icon/add.png new file mode 100644 index 0000000000000000000000000000000000000000..c717afb424cc757f74cdc254bce14c428eaed88a GIT binary patch literal 1973 zcmaJ?c~ld39uLPNP(e_spcRIpq6p+5awHNUB;gPPG=fAFHOUNS3dxMg1VT(fY&oJ7 zIf~e#RjD+^+8`-b+{&?Ns{$g`DwboFu7$OLz=AG{+D@$4{bT98H}gB*=kxhqpZ7)= z9~)_ByWEyUBH2YpaRtN*m|klu;;YnXrisM`=LzvmXeyp5QotlOgoBZhlO|z6U}y*stY#7dG8h*DYFWBm$yBqzm%2=1Z@Q*{z)J|8 z#sdFHO304~IH&>!{K)~n5*j@S2xO3H{)`~Mbv}R}l@>suGAOilzEm2M%3xAyz^ex& zqA8$MrhpsqDi(2Kfl?gDm=sE8W+pk4PDT~GC^QCxL81Cl{QP_ggs(D7j*Ha3a;3+d z0vA?F6bObRs2ngUio~c2XMseff0Q7@Udzgrui8X3jG`7{6dIXoDrpYL=l?ZSCVP!m z;sW^3eE+YoG9e3tDFRrDsuU7paH$@qP#BY=fJHc}NI=o_xh}>_Q5;oDQ4HX4OkE2E z)+8Zv2+dS_z2xxu%xJk17s(}XG?xVuDr5wKm;pgSY#xX17w*Sk(`dXv29?JP=Ww~~ za4Lhwrt#*uTvVcx!E$_#3;oHZ&&xI0L52~Lxv&DsfT0KlDg$1Y%|zzUg*va^D=svD zF0^^M6e1alX}JG0?71t#L8k3%>k^08=7;5k;}wLp4_z!>OCnjWisrHt)V&Q!Bl5n0 zr7xn|%f@B|tC615VFfRoAN#yl{N|o7o5Oyy|M%Am?;I4h-pJT;^vB6WW7Na9O3S={ z+77qEVF$p2UC*__jx%FU%?!=rtMH=h)!s$799EtHKKPGnd@^cvYLOjh^B&Lp3whu8 zOxxZpn2i&TxH)!gb}dYQ;t&Erh#HC#eXY^YqUTryMYdwcpwX#dhZ1CEI2^^<$u zyZS!ds&}y&?fz6%T(GD$Kf5y{a@mfOtz9)?PN|#oJBOSrU(^*p&*pm{@v`w|_ntqR zH+5e%brgwpUcF{#N>R*+3}{SV5qa~*($%)V6KTC}rRL9V46{jU$It>Wn|!6Z`0xhF7YiDzHo6}KkJKgIh{SQ*#;H)=&b;AKgScHanJ(;+M%X{BxLXn2F123K zzjD;1`)#zZ%OnuH5Pt6F!LE#s?wesEt|2BBjDm_OZl|h+`}Rglxv9&d=|c63XV<&IzhBv3d2Zd^ zAGF&SN7a4swr7eJ2*u+w= zYu|pBMBA->)?=m<SbzN3Eyst0Ao^X?SWbzx!xa5O>l>jKgGCr> to4mX0t>c=I{KJicon/img_flag.png img/havok_logo.png img/qhull_cone.gif + icon/add.png

NifSkope is free software available under a BSD license. The source is available via GitHub

icon/handle.png diff --git a/src/model/nifmodel.cpp b/src/model/nifmodel.cpp index b011a0cc1..122d96714 100644 --- a/src/model/nifmodel.cpp +++ b/src/model/nifmodel.cpp @@ -1046,6 +1046,16 @@ bool NifModel::inherits( const QString & name, const QString & aunty ) const return false; } +bool NifModel::inherits( const QString & name, const QStringList & ancestors ) const +{ + for ( const auto & a : ancestors ) { + if ( inherits( name, a ) ) + return true; + } + + return false; +} + bool NifModel::inherits( const QModelIndex & idx, const QString & aunty ) const { int x = getBlockNumber( idx ); diff --git a/src/model/nifmodel.h b/src/model/nifmodel.h index 44370f6d8..4a59d69c0 100644 --- a/src/model/nifmodel.h +++ b/src/model/nifmodel.h @@ -266,6 +266,8 @@ class NifModel final : public BaseModel bool isAncestorOrNiBlock( const QString & name ) const override final; //! Returns true if name inherits ancestor. bool inherits( const QString & name, const QString & ancestor ) const override final; + //! Returns true if name inherits any ancestors in list. + bool inherits( const QString & name, const QStringList & ancestors ) const; // returns true if the block containing index inherits ancestor bool inherits( const QModelIndex & index, const QString & ancestor ) const; diff --git a/src/spells/blocks.cpp b/src/spells/blocks.cpp index 333345e9b..87bee3ea5 100644 --- a/src/spells/blocks.cpp +++ b/src/spells/blocks.cpp @@ -21,6 +21,140 @@ * spRemoveBranch is declared in \link spells/blocks.h \endlink so that it is accessible to spCombiTris. */ +// Since nifxml doesn't track any of this data... + +//! The valid controller types for each block +QMultiMap ctlrMapping = { + { "NiObjectNET", "NiExtraDataController" }, + { "NiAVObject", "NiControllerManager" }, + { "NiAVObject", "NiVisController" }, + { "NiAVObject", "NiTransformController" }, + { "NiAVObject", "NiMultiTargetTransformController" }, + { "NiParticles", "NiPSysModifierCtlr" }, + { "NiParticles", "NiPSysUpdateCtlr" }, + { "NiParticles", "NiPSysResetOnLoopCtlr" }, + { "NiGeometry", "NiGeomMorpherController" }, + { "NiLight", "NiLightColorController" }, + { "NiLight", "NiLightDimmerController" }, + { "NiMaterialProperty", "NiAlphaController" }, + { "NiMaterialProperty", "NiMaterialColorController" }, + { "NiTexturingProperty", "NiFlipController" }, + { "NiTexturingProperty", "NiTextureTransformController" }, + // New Particles + { "NiPSParticleSystem", "NiPSEmitterCtlr" }, + { "NiPSParticleSystem", "NiPSForceCtlr" }, + { "NiPSParticleSystem", "NiPSResetOnLoopCtlr" }, + // New Geometry + { "NiMesh", "NiMorphWeightsController" }, +}; + +//! The valid controller types for each block, Bethesda-only +QMultiMap ctlrMappingBS = { + // OB+ + { "NiAVObject", "BSProceduralLightningController" }, + { "NiAlphaProperty", "BSNiAlphaPropertyTestRefController" }, + { "NiCamera", "BSFrustumFOVController" }, + { "NiNode", "bhkBlendController" }, + { "NiNode", "NiBSBoneLODController" }, + // FO3 + { "BSShaderPPLightingProperty", "BSRefractionFirePeriodController" }, + { "BSShaderPPLightingProperty", "BSRefractionStrengthController" }, + { "NiMaterialProperty", "BSMaterialEmittanceMultController" }, + // SK+ + { "NiNode", "BSLagBoneController" }, + { "BSEffectShaderProperty", "BSEffectShaderPropertyColorController" }, + { "BSEffectShaderProperty", "BSEffectShaderPropertyFloatController" }, + { "BSLightingShaderProperty", "BSLightingShaderPropertyColorController" }, + { "BSLightingShaderProperty", "BSLightingShaderPropertyFloatController" }, + // FO4 + { "NiLight", "NiLightRadiusController" }, +}; + +//! Blocks that are never used beyond 10.1.0.0 +QStringList legacyOnlyBlocks = { + "NiBone", + "NiImage", + "NiRawImageData", + "NiParticleModifier", + "NiParticleSystemController", + "NiTriShapeSkinController", + "NiEnvMappedTriShape", + "NiEnvMappedTriShapeData", + "NiTextureProperty", + "NiMultiTextureProperty", + "NiTransparentProperty", + // Morrowind + "AvoidNode", + "RootCollisionNode", + "NiBSAnimationNode", + "NiBSParticleNode" +}; + +//! Blocks that store data for NiTimeControllers +QStringList animationData = { + "NiPosData", + "NiRotData", + "NiBoolData", + "NiFloatData", + "NiColorData", + "NiTransformData", + "NiKeyframeData", + "NiMorphData", + "NiUVData", + "NiVisData", + "NiBSplineData", + "NiBSplineBasisData", + "NiDefaultAVObjectPalette" +}; + +//! The interpolators that return true for NiInterpolator::IsBoolValueSupported() +QStringList boolValue = { + "NiBoolInterpolator", + "NiBlendBoolInterpolator" +}; +//! The interpolators that return true for NiInterpolator::IsFloatValueSupported() +QStringList floatValue = { + "NiFloatInterpolator", + "NiBlendFloatInterpolator", + "NiBSplineFloatInterpolator" +}; +//! The interpolators that return true for NiInterpolator::IsPoint3ValueSupported() +QStringList point3Value = { + "NiPoint3Interpolator", + "NiBlendPoint3Interpolator", + "NiBSplinePoint3Interpolator" +}; +//! The interpolators that return true for NiInterpolator::IsTransformValueSupported() +QStringList transformValue = { + "NiTransformInterpolator", + "NiBlendTransformInterpolator", + "NiBlendAccumTransformInterpolator", + "NiBSplineTransformInterpolator", + "NiPathInterpolator", + "NiLookAtInterpolator" +}; + +//! The kind of interpolator values supported on each controller +QMultiMap interpMapping = +{ + { "NiBoolInterpController", boolValue }, + { "NiFloatInterpController", floatValue }, + { "NiPoint3InterpController", point3Value }, + { "NiFloatExtraDataController", floatValue }, + { "NiFloatsExtraDataController", floatValue }, + { "NiFloatsExtraDataPoint3Controller", point3Value }, + { "NiTransformController", transformValue }, + { "NiMultiTargetTransformController", transformValue }, + { "NiPSysEmitterCtlr", floatValue }, // Interpolator + { "NiPSysEmitterCtlr", boolValue }, // Visibility Interpolator + { "NiPSysModifierBoolCtlr", boolValue }, + { "NiPSEmitterFloatCtlr", floatValue }, + { "NiPSForceFloatCtlr", floatValue }, + { "NiPSForceBoolCtlr", boolValue }, + { "NiMorphWeightsController", floatValue }, + { "NiGeomMorpherController", floatValue }, +}; + //! Add a link to the specified block to a link array /*! * @param nif The model @@ -185,6 +319,153 @@ static void removeChildren( NifModel * nif, const QPersistentModelIndex & iBlock } } +//! Set values in blocks that cannot be handled in nif.xml such as inherited values +void blockDefaults( NifModel * nif, const QString & type, const QModelIndex & index ) +{ + // Set Bethesda NiExtraData names to their required strings + static QMap nameMap = { + {"BSBehaviorGraphExtraData", "BGED"}, + {"BSBoneLODExtraData", "BSBoneLOD"}, + {"BSBound", "BBX"}, + {"BSClothExtraData", "CED"}, + {"BSConnectPoint::Children", "CPT"}, + {"BSConnectPoint::Parents", "CPA"}, + {"BSDecalPlacementVectorExtraData", "DVPG"}, + {"BSDistantObjectLargeRefExtraData", "DOLRED"}, + {"BSEyeCenterExtraData", "ECED"}, + {"BSFurnitureMarker", "FRN"}, + {"BSFurnitureMarkerNode", "FRN"}, + {"BSInvMarker", "INV"}, + {"BSPositionData", "BSPosData"}, + {"BSWArray", "BSW"}, + {"BSXFlags", "BSX"}, + }; + + auto iterName = nameMap.find( type ); + if ( iterName != nameMap.end() ) + nif->set( nif->getIndex( index, "Name" ), iterName.value() ); +} + +//! Filters a list of blocks based on version (since nif.xml lacks this data) +void blockFilter( NifModel * nif, std::list& blocks, const QString & type = {} ) +{ + blocks.erase( std::remove_if( blocks.begin(), blocks.end(), + [nif, type] ( const QString& s ) { return !nif->inherits( s, type ) + // Obsolete/Undecoded + || s.startsWith( "NiClod" ) || s.startsWith( "NiArk" ) || s.startsWith( "NiBez" ) + || s.startsWith( "Ni3ds" ) || s.startsWith( "NiBinaryVox" ) + // Legacy + || ( ( (nif->inherits( s, "NiParticles" ) && !nif->inherits( s, "NiParticleSystem" )) + || (nif->inherits( s, "NiParticlesData" ) && !s.startsWith( "NiP" )) // NiRotating, NiAutoNormal, etc. + || nif->inherits( s, legacyOnlyBlocks ) ) + && nif->getVersionNumber() > 0x0a010000 ) + // Bethesda + || ( (s.startsWith( "bhk" ) || s.startsWith( "hk" ) || s.startsWith( "BS" ) + || s.endsWith( "ShaderProperty" )) && nif->getUserVersion2() == 0 ) + // Introduced in 20.2.0.8 + || (( s.startsWith( "NiPhysX" ) && nif->getVersionNumber() < 0x14020008 )) + // Introduced in 20.5 + || ( ((s.startsWith( "NiPS" ) && !s.contains( "PSys" )) || s.startsWith( "NiMesh" ) + || s.contains( "Evaluator" ) + ) && nif->getVersionNumber() < 0x14050000 ) + // Deprecated in 20.5 + || ( (s.startsWith( "NiParticle" ) || s.contains( "PSys" ) || s.startsWith( "NiTri" ) + || s.contains( "Interpolator" ) + ) && nif->getVersionNumber() >= 0x14050000 ); + } ), + blocks.end() + ); +} + +//! Creates a menu structure for a list of blocks +QMap blockMenu( NifModel * nif, const std::list & blocks, bool categorize = false, bool filter = false ) +{ + QMap map; + auto ids = blocks; + ids.sort(); + if ( filter ) + blockFilter( nif, ids ); + + bool firstCat = false; + for ( const QString& id : ids ) { + QString alph( "Other" ); + QString beth = (nif->getUserVersion2() == 0) ? alph : "Bethesda"; + QString hk = (nif->getUserVersion2() == 0) ? alph : "Havok"; + + bool alphabetized = false; + // Group Old Particles + if ( id.contains( "PSys" ) ) + alph = QString( "Ni&P(Sys)..." ); + // Group New Particles + else if ( id.startsWith( "NiPS" ) ) + alph = QString( "Ni&P(S)..." ); + // Group Havok + else if ( id.startsWith( "bhk" ) || id.startsWith( "hk" ) ) + alph = hk; + // Group PhysX + else if ( id.startsWith( "NiPhysX" ) ) + alph = "PhysX"; + // Group Bethesda + else if ( id.startsWith( "BS" ) || id.endsWith( "ShaderProperty" ) ) + alph = beth; + // Group Custom + else if ( !id.startsWith( "Ni" ) + || (id.startsWith( "NiBS" ) && !id.startsWith( "NiBSp" )) // Bethesda but not NiBSpline + || id.startsWith( "NiDeferred" ) ) + alph = "Other"; + // Alphabetize Everything else Ni + else if ( id.startsWith( "Ni" ) ) { + alph = QString( "Ni&" ) + id.mid( 2, 1 ) + "..."; + alphabetized = true; + } + + + // Categories + QString cat; + if ( !alphabetized ) + cat = ""; // Already grouped well above + else if ( nif->inherits( id, "NiInterpolator" ) || nif->inherits( id, "NiEvaluator" ) + || nif->inherits( id, "NiTimeController" ) + || id.contains( "Sequence" ) + || animationData.contains( id ) ) + cat = "NiAnimation..."; + else if ( nif->inherits( id, "NiNode" ) ) + cat = "NiNode..."; + else if ( nif->inherits( id, "NiGeometry" ) || nif->inherits( id, "NiGeometryData" ) + || id.contains( "Skin" ) ) + cat = "NiGeometry..."; + else if ( nif->inherits( id, "NiAVObject" ) ) + cat = "NiAVObject..."; + else if ( nif->inherits( id, "NiExtraData" ) ) + cat = "NiExtraData..."; + else if ( nif->inherits( id, "NiProperty" ) ) + cat = "NiProperty..."; + else if ( nif->inherits( id, "NiObject" ) ) + cat = "NiObject..."; + + if ( !map.contains( alph ) ) + map[alph] = new QMenu( alph ); + + map[alph]->addAction( id ); + + if ( categorize && !cat.isEmpty() ) { + if ( !firstCat ) { + // Use NiAAA to place it alphabetically between NiZBu and NiA[a-z] + // which will split the alphabetization and categorization. + map["NiAAA"] = new QMenu( "" ); + firstCat = true; + } + + if ( !map.contains( cat ) ) + map[cat] = new QMenu( cat ); + + map[cat]->addAction( id ); + } + } + + return map; +} + //! Insert an unattached block class spInsertBlock final : public Spell { @@ -200,34 +481,15 @@ class spInsertBlock final : public Spell QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final { - QStringList ids = nif->allNiBlocks(); - ids.sort(); - - QMap map; - for ( const QString& id : ids ) { - QString x( "Other" ); - - if ( id.startsWith( "Ni" ) ) - x = QString( "Ni&" ) + id.mid( 2, 1 ) + "..."; - - if ( id.startsWith( "bhk" ) || id.startsWith( "hk" ) ) - x = "Havok"; - - if ( id.startsWith( "BS" ) || id == "AvoidNode" || id == "RootCollisionNode" ) - x = "Bethesda"; - - if ( id.startsWith( "Fx" ) ) - x = "Firaxis"; - - if ( !map.contains( x ) ) - map[ x ] = new QMenu( x ); - - map[ x ]->addAction( id ); - } - QMenu menu; - for ( QMenu * m : map ) { - menu.addMenu( m ); + menu.addSection( tr( "Alphabetical" ) ); + for ( QMenu * m : blockMenu( nif, NifModel::allNiBlocks().toStdList(), true, true ) ) { + if ( m->title().isEmpty() ) + menu.addSection( tr( "Categories" ) ); + else if ( m->actions().size() == 1 ) + menu.addAction( m->actions().at( 0 ) ); + else + menu.addMenu( m ); } QAction * act = menu.exec( QCursor::pos() ); @@ -237,11 +499,7 @@ class spInsertBlock final : public Spell QModelIndex newindex = nif->insertNiBlock( act->text(), nif->getBlockNumber( index ) + 1 ); // Set values that can't be handled by defaults in nif.xml - if ( act->text() == "BSXFlags" ) { - nif->set( nif->getIndex( newindex, "Name" ), "BSX" ); - } else if ( act->text() == "BSBound" ) { - nif->set( nif->getIndex( newindex, "Name" ), "BBX" ); - } + blockDefaults( nif, act->text(), newindex ); // return index to new block return newindex; @@ -357,6 +615,128 @@ class spAttachNode final : public Spell REGISTER_SPELL( spAttachNode ) + +//! Attach a new block to an empty Ref link +class spAddNewRef final : public Spell +{ +public: + QString name() const override final { return Spell::tr( "Attach" ); } + bool instant() const { return true; } + QIcon icon() const { return QIcon( ":img/add" ); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final + { + auto val = nif->getValue( index ); + if ( val.type() == NifValue::tLink ) + return nif->isLink( index ) && nif->getLink( index ) == -1; + return false; + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final + { + NifItem * item = static_cast(index.internalPointer()); + auto type = item->temp(); + + std::list allIds = nif->allNiBlocks().toStdList(); + blockFilter( nif, allIds, type ); + + auto iBlock = nif->getBlock( index ); + + std::list ids; + auto ctlrFilter = [nif, &ids, &allIds, &iBlock] ( QMultiMap m ) { + auto i = m.begin(); + while ( i != m.end() ) { + if ( nif->inherits( nif->getBlockName( iBlock ), i.key() ) ) + for ( const auto & id : allIds ) + if ( nif->inherits( id, i.value() ) ) + ids.push_back( id ); + ++i; + } + }; + + auto interpFilter = [nif, &ids, &allIds, &iBlock]( QMultiMap m ) { + auto i = m.begin(); + while ( i != m.end() ) { + if ( nif->inherits( nif->getBlockName( iBlock ), i.key() ) ) + for ( const auto & id : allIds ) + for ( const auto & s : i.value() ) + if ( nif->inherits( id, s ) ) + ids.push_back( id ); + ++i; + } + }; + + if ( nif->inherits( type, "NiTimeController" ) ) { + // Show only applicable types for controller links for the given block + if ( nif->inherits( iBlock, "NiTimeController" ) && item->name() == "Next Controller" ) + iBlock = nif->getBlock( nif->getLink( index.parent(), "Target" ) ); + + if ( nif->getVersionNumber() > 0x14050000 ) { + ctlrMapping.insertMulti( "NiNode", "NiSkinningLODController" ); + } + // Block-to-Controller Mapping + ctlrFilter( ctlrMapping ); + // Bethesda Controllers + if ( nif->getUserVersion2() > 0 ) + ctlrFilter( ctlrMappingBS ); + + } else if ( nif->inherits( iBlock, "NiTimeController" ) + && nif->inherits( type, "NiInterpolator" ) ) { + // Show only applicable types for interpolator links for the given block + interpFilter( interpMapping ); + } else { + ids = allIds; + } + + ids.sort(); + ids.unique(); + + QMenu menu; + if ( ids.size() < 8 ) { + for ( const QString& id : ids ) + menu.addAction( id ); + } else { + for ( QMenu * m : blockMenu( nif, ids ) ) { + if ( m->actions().size() == 1 ) + menu.addAction( m->actions().at(0) ); + else + menu.addMenu( m ); + } + } + + QAction * act; + if ( ids.size() == 1 ) + act = menu.actions().at(0); + else + act = menu.exec( QCursor::pos() ); + + if ( act ) { + // insert block + QModelIndex newindex = nif->insertNiBlock( act->text(), nif->getBlockNumber( index ) + 1 ); + + if ( !nif->setLink( index, nif->getBlockNumber( newindex ) ) ) { + qCWarning( nsSpell ) << tr( "Failed to attach link." ); + } + + if ( nif->inherits( nif->getBlockName( newindex ), "NiTimeController" ) ) { + auto blk = nif->getBlockNumber( iBlock ); + nif->setLink( newindex, "Target", blk ); + } + + // Set values that can't be handled by defaults in nif.xml + blockDefaults( nif, act->text(), newindex ); + + // return index to new block + return newindex; + } + + return index; + } +}; + +REGISTER_SPELL( spAddNewRef ) + + //! Attach a dynamic effect (4/5 are lights) to a block class spAttachLight final : public Spell { @@ -437,12 +817,8 @@ class spAttachExtraData final : public Spell QPersistentModelIndex iParent = index; QModelIndex iExtra = nif->insertNiBlock( act->text(), nif->getBlockNumber( index ) + 1 ); - // fixup - if ( act->text() == "BSXFlags" ) { - nif->set( nif->getIndex( iExtra, "Name" ), "BSX" ); - } else if ( act->text() == "BSBound" ) { - nif->set( nif->getIndex( iExtra, "Name" ), "BBX" ); - } + // Set values that can't be handled by defaults in nif.xml + blockDefaults( nif, act->text(), iExtra ); addLink( nif, iParent, "Extra Data List", nif->getBlockNumber( iExtra ) ); return iExtra; From 323d67c6703cd40887b8a75b5399a0e085b3cf45 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sat, 16 Dec 2017 11:12:53 -0500 Subject: [PATCH 137/152] [UI] Major block copy/paste overhaul - String copy/paste for 20.1.0.1+ (switch to string indices) - Usage/Access are retained for NiDataStream with copy/paste - Relaxed the linking restrictions when a link points to the root node. This allows NiCollisionObject branches to be pasted onto other NIFs more easily for example. - Pasting a branch is 20-30x faster, more so for extremely deep branches. Speed increase came solely from pausing model updates during insertNiBlock. --- src/spells/blocks.cpp | 436 +++++++++++++++++++++++++++++++++++------- src/spells/blocks.h | 11 ++ 2 files changed, 377 insertions(+), 70 deletions(-) diff --git a/src/spells/blocks.cpp b/src/spells/blocks.cpp index 87bee3ea5..084a4f0a4 100644 --- a/src/spells/blocks.cpp +++ b/src/spells/blocks.cpp @@ -21,6 +21,17 @@ * spRemoveBranch is declared in \link spells/blocks.h \endlink so that it is accessible to spCombiTris. */ +const char * B_ERR = QT_TR_NOOP( "%1 failed with errors." ); + + // Use Unicode symbols for separators + // to lessen chance of splitting incorrectly. + // (Previous separators were `|` and `/`) +const char * NAME_SEP = "˃"; // This is Unicode U+02C3 +const char * MIME_SEP = "˂"; // This is Unicode U+02C2 +const char * STR_BR = "nifskope˂nibranch˂%1"; +const char * STR_BL = "nifskope˂niblock˂%1˂%2"; + + // Since nifxml doesn't track any of this data... //! The valid controller types for each block @@ -155,6 +166,185 @@ QMultiMap interpMapping = { "NiGeomMorpherController", floatValue }, }; +//! The string names which can appear in the block root +QStringList rootStringList = +{ + "Name", + "Modifier Name", // NiPSysModifierCtlr + "File Name", // NiSourceTexture + "String Data", // NiStringExtraData + "Extra Data Name", // NiExtraDataController + "Accum Root Name", // NiSequence + "Look At Name", // NiLookAtInterpolator + "Driven Name", // NiLookAtEvaluator + "Emitter Name", // NiPSEmitterCtlr + "Force Name", // NiPSForceCtlr + "Mesh Name", // NiPhysXMeshDesc + "Shape Name", // NiPhysXShapeDesc + "Actor Name", // NiPhysXActorDesc + "Joint Name", // NiPhysXJointDesc + "Wet Material", // BSLightingShaderProperty FO4+ + "Behaviour Graph File", // BSBehaviorGraphExtraData +}; + +//! Get strings array +QStringList getStringsArray( NifModel * nif, const QModelIndex & parent, + const QString & arr, const QString & name = {} ) +{ + QStringList strings; + auto iArr = nif->getIndex( parent, arr ); + if ( !iArr.isValid() ) + return {}; + + if ( name.isEmpty() ) { + for ( int i = 0; i < nif->rowCount( iArr ); i++ ) + strings << nif->string( iArr.child( i, 0 ) ); + } else { + for ( int i = 0; i < nif->rowCount( iArr ); i++ ) + strings << nif->string( iArr.child( i, 0 ), name, false ); + } + + return strings; +} +//! Set strings array +void setStringsArray( NifModel * nif, const QModelIndex & parent, QStringList & strings, + const QString & arr, const QString & name = {} ) +{ + auto iArr = nif->getIndex( parent, arr ); + if ( !iArr.isValid() ) + return; + + if ( name.isEmpty() ) { + for ( int i = 0; i < nif->rowCount( iArr ); i++ ) + nif->set( iArr.child( i, 0 ), strings.takeFirst() ); + } else { + for ( int i = 0; i < nif->rowCount( iArr ); i++ ) + nif->set( iArr.child( i, 0 ), name, strings.takeFirst() ); + } +} +//! Get "Name" et al. for NiObjectNET, NiExtraData, NiPSysModifier, etc. +QStringList getNiObjectRootStrings( NifModel * nif, const QModelIndex & iBlock ) +{ + QStringList strings; + for ( int i = 0; i < nif->rowCount( iBlock ); i++ ) { + auto iString = iBlock.child( i, 0 ); + if ( rootStringList.contains( nif->itemName( iString ) ) ) + strings << nif->string( iString ); + } + + return strings; +} +//! Set "Name" et al. for NiObjectNET, NiExtraData, NiPSysModifier, etc. +void setNiObjectRootStrings( NifModel * nif, const QModelIndex & iBlock, QStringList & strings ) +{ + for ( int i = 0; i < nif->rowCount( iBlock ); i++ ) { + auto iString = iBlock.child( i, 0 ); + if ( rootStringList.contains( nif->itemName( iString ) ) ) + nif->set( iString, strings.takeFirst() ); + } +} +//! Get strings for NiMesh +QStringList getStringsNiMesh( NifModel * nif, const QModelIndex & iBlock ) +{ + // "Datastreams/Component Semantics/Name" * "Num Datastreams" + QStringList strings; + auto iData = nif->getIndex( iBlock, "Datastreams" ); + if ( !iData.isValid() ) + return {}; + + for ( int i = 0; i < nif->rowCount( iData ); i++ ) + strings << getStringsArray( nif, iData.child( i, 0 ), "Component Semantics", "Name" ); + + return strings; +} +//! Set strings for NiMesh +void setStringsNiMesh( NifModel * nif, const QModelIndex & iBlock, QStringList & strings ) +{ + auto iData = nif->getIndex( iBlock, "Datastreams" ); + if ( !iData.isValid() ) + return; + + for ( int i = 0; i < nif->rowCount( iData ); i++ ) + setStringsArray( nif, iData.child( i, 0 ), strings, "Component Semantics", "Name" ); +} +//! Get strings for NiSequence +QStringList getStringsNiSequence( NifModel * nif, const QModelIndex & iBlock ) +{ + QStringList strings; + auto iControlledBlocks = nif->getIndex( iBlock, "Controlled Blocks" ); + if ( !iControlledBlocks.isValid() ) + return {}; + + for ( int i = 0; i < nif->rowCount( iControlledBlocks ); i++ ) { + auto iChild = iControlledBlocks.child( i, 0 ); + strings << nif->string( iChild, "Target Name", false ) + << nif->string( iChild, "Node Name", false ) + << nif->string( iChild, "Property Type", false ) + << nif->string( iChild, "Controller Type", false ) + << nif->string( iChild, "Controller ID", false ) + << nif->string( iChild, "Interpolator ID", false ); + } + + return strings; +} +//! Set strings for NiSequence +void setStringsNiSequence( NifModel * nif, const QModelIndex & iBlock, QStringList & strings ) +{ + auto iControlledBlocks = nif->getIndex( iBlock, "Controlled Blocks" ); + if ( !iControlledBlocks.isValid() ) + return; + + for ( int i = 0; i < nif->rowCount( iControlledBlocks ); i++ ) { + auto iChild = iControlledBlocks.child( i, 0 ); + nif->set( iChild, "Target Name", strings.takeFirst() ); + nif->set( iChild, "Node Name", strings.takeFirst() ); + nif->set( iChild, "Property Type", strings.takeFirst() ); + nif->set( iChild, "Controller Type", strings.takeFirst() ); + nif->set( iChild, "Controller ID", strings.takeFirst() ); + nif->set( iChild, "Interpolator ID", strings.takeFirst() ); + } +} + +//! Builds string list for datastream +QStringList serializeStrings( NifModel * nif, const QModelIndex & iBlock, const QString & type ) +{ + auto strings = getNiObjectRootStrings( nif, iBlock ); + if ( nif->inherits( type, "NiSequence" ) ) + strings << getStringsNiSequence( nif, iBlock ); + else if ( type == "NiTextKeyExtraData" ) + strings << getStringsArray( nif, iBlock, "Text Keys", "Value" ); + else if ( type == "NiMesh" ) + strings << getStringsNiMesh( nif, iBlock ); + else if ( type == "NiStringsExtraData" ) + strings << getStringsArray( nif, iBlock, "Data" ); + else if ( type == "NiMorphWeightsController" ) + strings << getStringsArray( nif, iBlock, "Target Names" ); + + if ( type == "NiMesh" || nif->inherits( type, "NiGeometry" ) ) + strings << getStringsArray( nif, nif->getIndex( iBlock, "Material Data" ), "Material Name" );; + + return strings; +} + +//! Consumes string list from datastream +void deserializeStrings( NifModel * nif, const QModelIndex & iBlock, const QString & type, QStringList & strings ) +{ + setNiObjectRootStrings( nif, iBlock, strings ); + if ( nif->inherits( type, "NiSequence" ) ) + setStringsNiSequence( nif, iBlock, strings ); + else if ( type == "NiTextKeyExtraData" ) + setStringsArray( nif, iBlock, strings, "Text Keys", "Value" ); + else if ( type == "NiMesh" ) + setStringsNiMesh( nif, iBlock, strings ); + else if ( type == "NiStringsExtraData" ) + setStringsArray( nif, iBlock, strings, "Data" ); + else if ( type == "NiMorphWeightsController" ) + setStringsArray( nif, iBlock, strings, "Target Names" ); + + if ( type == "NiMesh" || nif->inherits( type, "NiGeometry" ) ) + setStringsArray( nif, nif->getIndex( iBlock, "Material Data" ), strings, "Material Name" ); +} + //! Add a link to the specified block to a link array /*! * @param nif The model @@ -267,7 +457,7 @@ void blockLink( NifModel * nif, const QModelIndex & index, const QModelIndex & i //! Helper function for branch paste static qint32 getBlockByName( NifModel * nif, const QString & tn ) { - QStringList ls = tn.split( "|" ); + QStringList ls = tn.split( NAME_SEP ); QString type = ls.value( 0 ); QString name = ls.value( 1 ); @@ -868,10 +1058,19 @@ class spCopyBlock final : public Spell { QByteArray data; QBuffer buffer( &data ); + if ( !buffer.open( QIODevice::WriteOnly ) ) + return {}; - if ( buffer.open( QIODevice::WriteOnly ) && nif->saveIndex( buffer, index ) ) { + QDataStream ds( &buffer ); + + auto bType = nif->createRTTIName( index ); + + if ( nif->checkVersion( 0x14010001, 0 ) ) + ds << serializeStrings( nif, index, bType ); + + if ( nif->saveIndex( buffer, index ) ) { QMimeData * mime = new QMimeData; - mime->setData( QString( "nifskope/niblock/%1/%2" ).arg( nif->itemName( index ), nif->getVersion() ), data ); + mime->setData( QString( STR_BL ).arg( nif->getVersion(), bType ), data ); QApplication::clipboard()->setMimeData( mime ); } @@ -888,20 +1087,19 @@ class spPasteBlock final : public Spell QString name() const override final { return Spell::tr( "Paste" ); } QString page() const override final { return Spell::tr( "Block" ); } - QString acceptFormat( const QString & format, const NifModel * nif ) + QPair acceptFormat( const QString & format, const NifModel * nif ) { - Q_UNUSED( nif ); - QStringList split = format.split( "/" ); + QStringList split = format.split( MIME_SEP ); - if ( split.value( 0 ) == "nifskope" && split.value( 1 ) == "niblock" && nif->isNiBlock( split.value( 2 ) ) ) - return split.value( 3 ); + NiMesh::DataStreamMetadata metadata = {}; + auto bType = nif->extractRTTIArgs( split.value( MIME_IDX_TYPE ), metadata ); + if ( !NifModel::isNiBlock( bType ) ) + return {}; - return QString(); - } + if ( split.value( MIME_IDX_APP ) == "nifskope" && split.value( MIME_IDX_STREAM ) == "niblock" ) + return {split.value( MIME_IDX_VER ), bType}; - QString blockType( const QString & format ) - { - return format.split( "/" ).value( 2 ); + return {}; } bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final @@ -911,7 +1109,7 @@ class spPasteBlock final : public Spell if ( mime ) { for ( const QString& form : mime->formats() ) { - if ( !acceptFormat( form, nif ).isEmpty() ) + if ( !acceptFormat( form, nif ).first.isEmpty() ) return true; } } @@ -925,16 +1123,45 @@ class spPasteBlock final : public Spell if ( mime ) { for ( const QString& form : mime->formats() ) { - QString version = acceptFormat( form, nif ); - - if ( !version.isEmpty() && ( version == nif->getVersion() || QMessageBox::question( 0, Spell::tr( "Paste Block" ), Spell::tr( "Nif versions differ!

Current File Version: %1
Clipboard Data Version: %2

The results will be unpredictable..." ).arg( nif->getVersion() ).arg( version ), Spell::tr( "Continue" ), Spell::tr( "Cancel" ) ) == 0 ) ) { + auto result = acceptFormat( form, nif ); + auto version = result.first; + + NiMesh::DataStreamMetadata metadata = {}; + auto bType = nif->extractRTTIArgs( result.second, metadata ); + + if ( !version.isEmpty() + && (version == nif->getVersion() || QMessageBox::question( nullptr, + tr( "Paste Block" ), + tr( "Nif versions differ!

Current File Version: %1
Clipboard Data Version: %2

The results will be unpredictable..." ) + .arg( nif->getVersion() ) + .arg( version ), + tr( "Continue" ), + tr( "Cancel" ) ) == 0) + ) { QByteArray data = mime->data( form ); QBuffer buffer( &data ); if ( buffer.open( QIODevice::ReadOnly ) ) { - QModelIndex block = nif->insertNiBlock( blockType( form ), nif->getBlockCount() ); + QDataStream ds( &buffer ); + QStringList strings; + ds >> strings; + + QModelIndex block = nif->insertNiBlock( bType, nif->getBlockCount() ); nif->loadIndex( buffer, block ); blockLink( nif, index, block ); + + // Post-Load corrections + + // NiDataStream RTTI arg values + if ( nif->checkVersion( 0x14050000, 0 ) && bType == QLatin1String( "NiDataStream" ) ) { + nif->set( block, "Usage", metadata.usage ); + nif->set( block, "Access", metadata.access ); + } + + // Set strings + if ( nif->checkVersion( 0x14010001, 0 ) ) + deserializeStrings( nif, block, bType, strings ); + return block; } } @@ -955,14 +1182,20 @@ class spPasteOverBlock final : public Spell QString page() const override final { return Spell::tr( "Block" ); } QKeySequence hotkey() const override final { return{ Qt::CTRL + Qt::SHIFT + Qt::Key_V }; } - QString acceptFormat( const QString & format, const NifModel * nif, const QModelIndex & block ) + QPair acceptFormat( const QString & format, const NifModel * nif, const QModelIndex & iBlock ) { - QStringList split = format.split( "/" ); + QStringList split = format.split( MIME_SEP ); - if ( split.value( 0 ) == "nifskope" && split.value( 1 ) == "niblock" && nif->isNiBlock( block, split.value( 2 ) ) ) - return split.value( 3 ); + NiMesh::DataStreamMetadata metadata = {}; + auto bType = nif->extractRTTIArgs( split.value( MIME_IDX_TYPE ), metadata ); + if ( !nif->isNiBlock( iBlock, bType ) ) + return {}; - return QString(); + if ( split.value( MIME_IDX_APP ) == "nifskope" + && split.value( MIME_IDX_STREAM ) == "niblock" ) + return {split.value( MIME_IDX_VER ), bType}; + + return {}; } bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final @@ -971,7 +1204,7 @@ class spPasteOverBlock final : public Spell if ( mime ) { for ( const QString& form : mime->formats() ) { - if ( !acceptFormat( form, nif, index ).isEmpty() ) + if ( !acceptFormat( form, nif, index ).first.isEmpty() ) return true; } } @@ -985,14 +1218,41 @@ class spPasteOverBlock final : public Spell if ( mime ) { for ( const QString& form : mime->formats() ) { - QString version = acceptFormat( form, nif, index ); - - if ( !version.isEmpty() && ( version == nif->getVersion() || QMessageBox::question( 0, Spell::tr( "Paste Over" ), Spell::tr( "Nif versions differ!

Current File Version: %1
Clipboard Data Version: %2

The results will be unpredictable..." ).arg( nif->getVersion() ).arg( version ), Spell::tr( "Continue" ), Spell::tr( "Cancel" ) ) == 0 ) ) { + auto result = acceptFormat( form, nif, index ); + auto version = result.first; + + NiMesh::DataStreamMetadata metadata = {}; + auto bType = nif->extractRTTIArgs( result.second, metadata ); + + if ( !version.isEmpty() + && (version == nif->getVersion() || QMessageBox::question( nullptr, + tr( "Paste Over" ), + tr( "Nif versions differ!

Current File Version: %1
Clipboard Data Version: %2

The results will be unpredictable..." ) + .arg( nif->getVersion() ) + .arg( version ), + tr( "Continue" ), + tr( "Cancel" ) ) == 0) ) + { QByteArray data = mime->data( form ); QBuffer buffer( &data ); - if ( buffer.open( QIODevice::ReadOnly ) ) { + QDataStream ds( &buffer ); + + QStringList strings; + ds >> strings; + nif->loadIndex( buffer, index ); + + // NiDataStream RTTI arg values + if ( nif->checkVersion( 0x14050000, 0 ) && bType == QLatin1String( "NiDataStream" ) ) { + nif->set( index, "Usage", metadata.usage ); + nif->set( index, "Access", metadata.access ); + } + + // Set strings + if ( nif->checkVersion( 0x14010001, 0 ) ) + deserializeStrings( nif, index, bType, strings ); + return index; } } @@ -1035,17 +1295,19 @@ QModelIndex spCopyBranch::cast( NifModel * nif, const QModelIndex & index ) QString name = nif->get( iParent, "Name" ); if ( !name.isEmpty() ) { - parentMap.insert( link, nif->itemName( iParent ) + "|" + name ); + parentMap.insert( link, nif->itemName( iParent ) + NAME_SEP + name ); continue; } } - Message::critical( nullptr, Spell::tr( "%1 failed with errors." ).arg( name() ), Spell::tr( "failed to map parent link %1 %2 for block %3 %4; %5." ) - .arg( link ) - .arg( nif->itemName( nif->getBlock( link ) ) ) - .arg( block ) - .arg( nif->itemName( nif->getBlock( block ) ) ) - .arg( failMessage ) + Message::append( tr( B_ERR ).arg( name() ), + tr( "failed to map parent link %1 %2 for block %3 %4; %5." ) + .arg( link ) + .arg( nif->itemName( nif->getBlock( link ) ) ) + .arg( block ) + .arg( nif->itemName( nif->getBlock( block ) ) ) + .arg( failMessage ), + QMessageBox::Critical ); return index; } @@ -1060,19 +1322,27 @@ QModelIndex spCopyBranch::cast( NifModel * nif, const QModelIndex & index ) ds << blocks.count(); ds << blockMap; ds << parentMap; + for ( const auto block : blocks ) { - ds << nif->itemName( nif->getBlock( block ) ); + auto iBlock = nif->getBlock( block ); + auto bType = nif->createRTTIName( iBlock ); - if ( !nif->saveIndex( buffer, nif->getBlock( block ) ) ) { - Message::critical( nullptr, Spell::tr( "%1 failed with errors." ).arg( name() ), Spell::tr( "failed to save block %1 %2." ) - .arg( block ) - .arg( nif->itemName( nif->getBlock( block ) ) ) + ds << bType; + + if ( nif->checkVersion( 0x14010001, 0 ) ) + ds << serializeStrings( nif, iBlock, bType ); + + if ( !nif->saveIndex( buffer, iBlock ) ) { + Message::append( tr( B_ERR ).arg( name() ), + tr( "failed to save block %1 %2." ).arg( block ).arg( bType ), + QMessageBox::Critical ); return index; } } + QMimeData * mime = new QMimeData; - mime->setData( QString( "nifskope/nibranch/%1" ).arg( nif->getVersion() ), data ); + mime->setData( QString( STR_BR ).arg( nif->getVersion() ), data ); QApplication::clipboard()->setMimeData( mime ); } @@ -1087,10 +1357,10 @@ REGISTER_SPELL( spCopyBranch ) QString spPasteBranch::acceptFormat( const QString & format, const NifModel * nif ) { Q_UNUSED( nif ); - QStringList split = format.split( "/" ); + QStringList split = format.split( MIME_SEP ); - if ( split.value( 0 ) == "nifskope" && split.value( 1 ) == "nibranch" ) - return split.value( 2 ); + if ( split.value( MIME_IDX_APP ) == "nifskope" && split.value( MIME_IDX_STREAM ) == "nibranch" ) + return split.value( MIME_IDX_VER ); return QString(); } @@ -1122,10 +1392,10 @@ QModelIndex spPasteBranch::cast( NifModel * nif, const QModelIndex & index ) if ( !v.isEmpty() && ( v == nif->getVersion() - || QMessageBox::question( 0, Spell::tr( "Paste Branch" ), - Spell::tr( "Nif versions differ!

Current File Version: %1
Clipboard Data Version: %2

The results will be unpredictable..." ) - .arg( nif->getVersion() ).arg( v ), Spell::tr( "Continue" ), - Spell::tr( "Cancel" ) + || QMessageBox::question( 0, tr( "Paste Branch" ), + tr( "Nif versions differ!

Current File Version: %1
Clipboard Data Version: %2

The results will be unpredictable..." ) + .arg( nif->getVersion() ).arg( v ), tr( "Continue" ), + tr( "Cancel" ) ) == 0 ) ) @@ -1143,9 +1413,10 @@ QModelIndex spPasteBranch::cast( NifModel * nif, const QModelIndex & index ) ds >> blockMap; QMutableMapIterator ibm( blockMap ); + auto origBlockCount = nif->getBlockCount(); while ( ibm.hasNext() ) { ibm.next(); - ibm.value() += nif->getBlockCount(); + ibm.value() += origBlockCount; } QMap parentMap; @@ -1157,11 +1428,15 @@ QModelIndex spPasteBranch::cast( NifModel * nif, const QModelIndex & index ) ipm.next(); qint32 block = getBlockByName( nif, ipm.value() ); - if ( block >= 0 ) { + if ( ipm.key() == 0 ) { + // Ignore Root + blockMap.insert( ipm.key(), 0 ); + } else if ( block > 0 ) { blockMap.insert( ipm.key(), block ); } else { - Message::critical( nullptr, Spell::tr( "%1 failed with errors." ).arg( name() ), Spell::tr( "failed to map parent link %1" ) - .arg( ipm.value() ) + Message::append( tr( B_ERR ).arg( name() ), + tr( "failed to map parent link %1" ).arg( ipm.value() ), + QMessageBox::Critical ); return index; } @@ -1169,18 +1444,35 @@ QModelIndex spPasteBranch::cast( NifModel * nif, const QModelIndex & index ) QModelIndex iRoot; + nif->holdUpdates( true ); for ( int c = 0; c < count; c++ ) { - QString type; - ds >> type; + QString bType; + QStringList strings; + ds >> bType; + ds >> strings; - QModelIndex block = nif->insertNiBlock( type, -1 ); + NiMesh::DataStreamMetadata metadata = {}; + bType = nif->extractRTTIArgs( bType, metadata ); + QModelIndex block = nif->insertNiBlock( bType, -1 ); if ( !nif->loadAndMapLinks( buffer, block, blockMap ) ) return index; + // NiDataStream RTTI arg values + if ( nif->checkVersion( 0x14050000, 0 ) && bType == QLatin1String( "NiDataStream" ) ) { + nif->set( block, "Usage", metadata.usage ); + nif->set( block, "Access", metadata.access ); + } + + // Set strings + if ( nif->checkVersion( 0x14010001, 0 ) ) + deserializeStrings( nif, block, bType, strings ); + + if ( c == 0 ) iRoot = block; } + nif->holdUpdates( false ); blockLink( nif, index, iRoot ); @@ -1212,7 +1504,7 @@ class spPasteBranch2 final : public Spell QString acceptFormat( const QString & format, const NifModel * nif ) { Q_UNUSED( nif ); - QStringList split = format.split( "/" ); + QStringList split = format.split( MIME_SEP ); if ( split.value( 0 ) == "nifskope" && split.value( 1 ) == "nibranch" ) return split.value( 2 ); @@ -1590,17 +1882,19 @@ QModelIndex spDuplicateBranch::cast( NifModel * nif, const QModelIndex & index ) QString name = nif->get( iParent, "Name" ); if ( !name.isEmpty() ) { - parentMap.insert( link, nif->itemName( iParent ) + "|" + name ); + parentMap.insert( link, nif->itemName( iParent ) + NAME_SEP + name ); continue; } } - Message::critical( nullptr, Spell::tr( "%1 failed with errors." ).arg( name() ), Spell::tr( "failed to map parent link %1 %2 for block %3 %4; %5." ) - .arg( link ) - .arg( nif->itemName( nif->getBlock( link ) ) ) - .arg( block ) - .arg( nif->itemName( nif->getBlock( block ) ) ) - .arg( failMessage ) + Message::append( tr( B_ERR ).arg( name() ), + tr( "failed to map parent link %1 %2 for block %3 %4; %5." ) + .arg( link ) + .arg( nif->itemName( nif->getBlock( link ) ) ) + .arg( block ) + .arg( nif->itemName( nif->getBlock( block ) ) ) + .arg( failMessage ), + QMessageBox::Critical ); return index; } @@ -1619,9 +1913,10 @@ QModelIndex spDuplicateBranch::cast( NifModel * nif, const QModelIndex & index ) ds << nif->itemName( nif->getBlock( block ) ); if ( !nif->saveIndex( buffer, nif->getBlock( block ) ) ) { - Message::critical( nullptr, Spell::tr( "%1 failed with errors." ).arg( name() ), Spell::tr( "failed to save block %1 %2." ) - .arg( block ) - .arg( nif->itemName( nif->getBlock( block ) ) ) + Message::append( tr( B_ERR ).arg( name() ), + tr( "failed to save block %1 %2." ).arg( block ) + .arg( nif->itemName( nif->getBlock( block ) ) ), + QMessageBox::Critical ); return index; } @@ -1656,15 +1951,16 @@ QModelIndex spDuplicateBranch::cast( NifModel * nif, const QModelIndex & index ) if ( block >= 0 ) { blockMap.insert( ipm.key(), block ); } else { - Message::critical( nullptr, Spell::tr( "%1 failed with errors." ).arg( name() ), Spell::tr( "failed to map parent link %1" ) - .arg( ipm.value() ) + Message::append( tr( B_ERR ).arg( name() ), + tr( "failed to map parent link %1" ).arg( ipm.value() ), + QMessageBox::Critical ); return index; } } QModelIndex iRoot; - + nif->holdUpdates( true ); for ( int c = 0; c < count; c++ ) { QString type; ds >> type; @@ -1677,7 +1973,7 @@ QModelIndex spDuplicateBranch::cast( NifModel * nif, const QModelIndex & index ) if ( c == 0 ) iRoot = block; } - + nif->holdUpdates( false ); blockLink( nif, nif->getBlock( nif->getParent( nif->getBlockNumber( index ) ) ), iRoot ); return iRoot; diff --git a/src/spells/blocks.h b/src/spells/blocks.h index 65cc64456..f8e75843c 100644 --- a/src/spells/blocks.h +++ b/src/spells/blocks.h @@ -11,6 +11,17 @@ * All classes here inherit from the Spell class. */ +typedef enum { + // "nifskope" + MIME_IDX_APP = 0, + // "nibranch" or "niblock" + MIME_IDX_STREAM, + // "version" + MIME_IDX_VER, + // "type" + MIME_IDX_TYPE +} CopyPasteMimeTypes; + //! Copy a branch (a block and its descendents) to the clipboard class spCopyBranch final : public Spell { From 31fc1b2a16a3a05960d2ff369b7b4f398060e855 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sat, 16 Dec 2017 17:47:14 -0500 Subject: [PATCH 138/152] [GL] Renderer refactor, stage 1 Changed nearly all of setupProgram for the shaders. Store the uniform locations, use enums and maps instead of strings to refer to the uniforms. This helps with speed, legibility, and autocompletion. Removed all the `ob_` shaders for the time being as there is a shader complexity issue (more shaders == worse). Will reinstate fo3 and ob shading over time and with far fewer shaders. --- res/shaders/fo4_default.frag | 25 ++ res/shaders/fo4_default.prog | 18 +- res/shaders/fo4_default.vert | 34 +- res/shaders/fo4_effectshader.frag | 8 +- res/shaders/fo4_effectshader.prog | 18 +- res/shaders/fo4_effectshader.vert | 42 ++- res/shaders/fo4_env.frag | 329 ------------------ res/shaders/fo4_env.prog | 30 -- res/shaders/fo4_env.vert | 37 -- res/shaders/ob_glowmap.vert | 37 -- res/shaders/ob_material_default.vert | 43 --- res/shaders/ob_normal+glowmap.prog | 28 -- res/shaders/ob_normal+glowmap_vcol.prog | 28 -- res/shaders/ob_normalglowmap.frag | 132 -------- res/shaders/ob_normalmap.frag | 129 ------- res/shaders/ob_normalmap.prog | 33 -- res/shaders/ob_normalmap_vcol_ad.prog | 33 -- res/shaders/ob_normalmap_vcol_e.prog | 34 -- res/shaders/ob_parallax+glowmap.prog | 11 - res/shaders/ob_parallax.frag | 36 -- res/shaders/ob_parallax.prog | 19 -- res/shaders/ob_parallax_vcol_ad.prog | 21 -- res/shaders/ob_parallax_vcol_e.prog | 17 - res/shaders/ob_parallaxglowmap.frag | 38 --- res/shaders/ob_vcolors_ad.vert | 43 --- res/shaders/ob_vcolors_e.vert | 43 --- res/shaders/sk_default.frag | 52 ++- res/shaders/sk_default.prog | 35 +- res/shaders/sk_default.vert | 34 +- res/shaders/sk_effectshader.frag | 4 +- res/shaders/sk_effectshader.prog | 22 +- res/shaders/sk_effectshader.vert | 33 +- res/shaders/sk_env.frag | 158 --------- res/shaders/sk_env.prog | 30 -- res/shaders/sk_env.vert | 38 --- res/shaders/sk_glowmap.frag | 133 -------- res/shaders/sk_glowmap.prog | 27 -- res/shaders/sk_glowmap.vert | 38 --- res/shaders/sk_msn.frag | 1 - res/shaders/sk_msn.prog | 10 +- res/shaders/sk_multilayer.prog | 26 +- res/shaders/sk_multilayer.vert | 38 --- res/shaders/sk_parallax.frag | 130 -------- res/shaders/sk_parallax.prog | 27 -- src/gl/glmesh.cpp | 3 + src/gl/glmesh.h | 4 +- src/gl/renderer.cpp | 426 ++++++++++++------------ src/gl/renderer.h | 177 +++++++++- src/glview.cpp | 6 +- 49 files changed, 630 insertions(+), 2088 deletions(-) delete mode 100644 res/shaders/fo4_env.frag delete mode 100644 res/shaders/fo4_env.prog delete mode 100644 res/shaders/fo4_env.vert delete mode 100644 res/shaders/ob_glowmap.vert delete mode 100644 res/shaders/ob_material_default.vert delete mode 100644 res/shaders/ob_normal+glowmap.prog delete mode 100644 res/shaders/ob_normal+glowmap_vcol.prog delete mode 100644 res/shaders/ob_normalglowmap.frag delete mode 100644 res/shaders/ob_normalmap.frag delete mode 100644 res/shaders/ob_normalmap.prog delete mode 100644 res/shaders/ob_normalmap_vcol_ad.prog delete mode 100644 res/shaders/ob_normalmap_vcol_e.prog delete mode 100644 res/shaders/ob_parallax+glowmap.prog delete mode 100644 res/shaders/ob_parallax.frag delete mode 100644 res/shaders/ob_parallax.prog delete mode 100644 res/shaders/ob_parallax_vcol_ad.prog delete mode 100644 res/shaders/ob_parallax_vcol_e.prog delete mode 100644 res/shaders/ob_parallaxglowmap.frag delete mode 100644 res/shaders/ob_vcolors_ad.vert delete mode 100644 res/shaders/ob_vcolors_e.vert delete mode 100644 res/shaders/sk_env.frag delete mode 100644 res/shaders/sk_env.prog delete mode 100644 res/shaders/sk_env.vert delete mode 100644 res/shaders/sk_glowmap.frag delete mode 100644 res/shaders/sk_glowmap.prog delete mode 100644 res/shaders/sk_glowmap.vert delete mode 100644 res/shaders/sk_multilayer.vert delete mode 100644 res/shaders/sk_parallax.frag delete mode 100644 res/shaders/sk_parallax.prog diff --git a/res/shaders/fo4_default.frag b/res/shaders/fo4_default.frag index 76da4b9cc..ad1c708fd 100644 --- a/res/shaders/fo4_default.frag +++ b/res/shaders/fo4_default.frag @@ -1,4 +1,5 @@ #version 130 +#extension GL_ARB_shader_texture_lod : require uniform sampler2D BaseMap; uniform sampler2D NormalMap; @@ -6,6 +7,8 @@ uniform sampler2D GlowMap; uniform sampler2D BacklightMap; uniform sampler2D SpecularMap; uniform sampler2D GreyscaleMap; +uniform sampler2D EnvironmentMap; +uniform samplerCube CubeMap; uniform vec3 specColor; uniform float specStrength; @@ -30,6 +33,8 @@ uniform bool hasSoftlight; uniform bool hasBacklight; uniform bool hasRimlight; uniform bool hasTintColor; +uniform bool hasCubeMap; +uniform bool hasEnvMask; uniform bool hasSpecularMap; uniform bool greyscaleColor; uniform bool doubleSided; @@ -38,6 +43,8 @@ uniform float subsurfaceRolloff; uniform float rimPower; uniform float backlightPower; +uniform float envReflection; + uniform mat4 worldMatrix; in vec3 LightDir; @@ -229,6 +236,10 @@ void main( void ) float VdotH = max( dot(V, H), FLT_EPSILON ); float NdotNegL = max( dot(normal, -L), FLT_EPSILON ); + vec3 reflected = reflect( V, normal ); + vec3 reflectedVS = b * reflected.x + t * reflected.y + N * reflected.z; + vec3 reflectedWS = vec3( worldMatrix * (gl_ModelViewMatrixInverse * vec4( reflectedVS, 0.0 )) ); + vec4 color; vec3 albedo = baseMap.rgb * C.rgb; vec3 diffuse = A.rgb + D.rgb * NdotL0; @@ -264,6 +275,20 @@ void main( void ) spec = TorranceSparrow( NdotL0, NdotH, NdotV, VdotH, vec3(specMask), fSpecularPower, 0.2 ) * NdotL0 * D.rgb * specColor; } + // Environment + vec4 cube = textureLod( CubeMap, reflectedWS, 8.0 - smoothness * 8.0 ); + vec4 env = texture2D( EnvironmentMap, offset ); + if ( hasCubeMap ) { + cube.rgb *= envReflection * specStrength; + if ( hasEnvMask ) { + cube.rgb *= env.r; + } else { + cube.rgb *= s; + } + + spec += cube.rgb * diffuse; + } + vec3 backlight = vec3(0.0); if ( backlightPower > 0.0 ) { backlight = albedo * NdotNegL * clamp( backlightPower, 0.0, 1.0 ); diff --git a/res/shaders/fo4_default.prog b/res/shaders/fo4_default.prog index 8e7b44bd4..69dfa7618 100644 --- a/res/shaders/fo4_default.prog +++ b/res/shaders/fo4_default.prog @@ -1,19 +1,11 @@ # default shader -checkgroup begin or - # Fallout 4 and later - checkgroup begin and - check HEADER/Version >= 0x14020007 - check HEADER/User Version >= 12 - check HEADER/User Version 2 >= 130 - checkgroup end -checkgroup end - checkgroup begin and + # Fallout 4 + check HEADER/User Version 2 >= 130 check BSLightingShaderProperty - check BSLightingShaderProperty/Skyrim Shader Type != 1 - check BSLightingShaderProperty/Skyrim Shader Type != 16 - #check NiAVObject/Vertex Size >= 3 + #check BSLightingShaderProperty/Skyrim Shader Type != 1 + #check BSLightingShaderProperty/Skyrim Shader Type != 16 checkgroup begin or check BSTriShape check BSSubIndexTriShape @@ -24,5 +16,7 @@ checkgroup end texcoords 0 base texcoords 1 tangents texcoords 2 bitangents +texcoords 3 indices +texcoords 4 weights shaders fo4_default.vert fo4_default.frag diff --git a/res/shaders/fo4_default.vert b/res/shaders/fo4_default.vert index ed72d8219..beb372072 100644 --- a/res/shaders/fo4_default.vert +++ b/res/shaders/fo4_default.vert @@ -13,20 +13,40 @@ out vec4 C; out vec4 D; +uniform bool isGPUSkinned; +uniform mat4 boneTransforms[100]; + void main( void ) { - gl_Position = ftransform(); + gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; gl_TexCoord[0] = gl_MultiTexCoord0; - N = normalize(gl_NormalMatrix * gl_Normal); - t = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz); - b = normalize(gl_NormalMatrix * gl_MultiTexCoord2.xyz); - + if ( !isGPUSkinned ) { + N = normalize(gl_NormalMatrix * gl_Normal); + t = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz); + b = normalize(gl_NormalMatrix * gl_MultiTexCoord2.xyz); + v = vec3(gl_ModelViewMatrix * gl_Vertex); + } else { + mat4 bt = boneTransforms[int(gl_MultiTexCoord3[0])] * gl_MultiTexCoord4[0]; + bt += boneTransforms[int(gl_MultiTexCoord3[1])] * gl_MultiTexCoord4[1]; + bt += boneTransforms[int(gl_MultiTexCoord3[2])] * gl_MultiTexCoord4[2]; + bt += boneTransforms[int(gl_MultiTexCoord3[3])] * gl_MultiTexCoord4[3]; + + vec4 V = bt * gl_Vertex; + vec3 normal = vec3(bt * vec4(gl_Normal, 0.0)); + vec3 tan = vec3(bt * vec4(gl_MultiTexCoord1.xyz, 0.0)); + vec3 bit = vec3(bt * vec4(gl_MultiTexCoord2.xyz, 0.0)); + + gl_Position = gl_ModelViewProjectionMatrix * V; + N = normalize(gl_NormalMatrix * normal); + t = normalize(gl_NormalMatrix * tan); + b = normalize(gl_NormalMatrix * bit); + v = vec3(gl_ModelViewMatrix * V); + } + mat3 tbnMatrix = mat3(b.x, t.x, N.x, b.y, t.y, N.y, b.z, t.z, N.z); - - v = vec3(gl_ModelViewMatrix * gl_Vertex); ViewDir = tbnMatrix * -v.xyz; LightDir = tbnMatrix * gl_LightSource[0].position.xyz; diff --git a/res/shaders/fo4_effectshader.frag b/res/shaders/fo4_effectshader.frag index 710753b40..dacdef911 100644 --- a/res/shaders/fo4_effectshader.frag +++ b/res/shaders/fo4_effectshader.frag @@ -1,6 +1,6 @@ #version 130 -uniform sampler2D SourceTexture; +uniform sampler2D BaseMap; uniform sampler2D GreyscaleMap; uniform samplerCube CubeMap; uniform sampler2D NormalMap; @@ -57,7 +57,7 @@ void main( void ) { vec2 offset = gl_TexCoord[0].st * uvScale + uvOffset; - vec4 baseMap = texture2D( SourceTexture, offset ); + vec4 baseMap = texture2D( BaseMap, offset ); vec4 normalMap = texture2D( NormalMap, offset ); vec4 specMap = texture2D( SpecularMap, offset ); @@ -110,13 +110,13 @@ void main( void ) color.a = alphaMult * C.a * baseMap.a; if ( greyscaleColor ) { - vec4 luG = colorLookup( texture2D( SourceTexture, offset ).g, baseColor.r * C.r * falloff ); + vec4 luG = colorLookup( texture2D( BaseMap, offset ).g, baseColor.r * C.r * falloff ); color.rgb = luG.rgb; } if ( greyscaleAlpha ) { - vec4 luA = colorLookup( texture2D( SourceTexture, offset ).a, color.a ); + vec4 luA = colorLookup( texture2D( BaseMap, offset ).a, color.a ); color.a = luA.a; } diff --git a/res/shaders/fo4_effectshader.prog b/res/shaders/fo4_effectshader.prog index 355177175..f8ed3d5ba 100644 --- a/res/shaders/fo4_effectshader.prog +++ b/res/shaders/fo4_effectshader.prog @@ -1,23 +1,15 @@ # effect shader -checkgroup begin or - # Fallout 4 and later - checkgroup begin and - check HEADER/Version >= 0x14020007 - check HEADER/User Version >= 12 - check HEADER/User Version 2 >= 130 - checkgroup end -checkgroup end - -checkgroup begin or +checkgroup begin and # Fallout 4 - checkgroup begin and - check BSEffectShaderProperty - checkgroup end + check HEADER/User Version 2 >= 130 + check BSEffectShaderProperty checkgroup end texcoords 0 base texcoords 1 tangents texcoords 2 bitangents +texcoords 3 indices +texcoords 4 weights shaders fo4_effectshader.vert fo4_effectshader.frag diff --git a/res/shaders/fo4_effectshader.vert b/res/shaders/fo4_effectshader.vert index 909d462fa..de5b7a29c 100644 --- a/res/shaders/fo4_effectshader.vert +++ b/res/shaders/fo4_effectshader.vert @@ -3,29 +3,49 @@ out vec3 LightDir; out vec3 ViewDir; -out vec4 A; -out vec4 C; -out vec4 D; - out vec3 N; out vec3 t; out vec3 b; out vec3 v; +out vec4 A; +out vec4 C; +out vec4 D; + +uniform bool isGPUSkinned; +uniform mat4 boneTransforms[100]; + void main( void ) { - gl_Position = ftransform(); + gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; gl_TexCoord[0] = gl_MultiTexCoord0; - N = normalize(gl_NormalMatrix * gl_Normal); - t = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz); - b = normalize(gl_NormalMatrix * gl_MultiTexCoord2.xyz); + if ( !isGPUSkinned ) { + N = normalize(gl_NormalMatrix * gl_Normal); + t = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz); + b = normalize(gl_NormalMatrix * gl_MultiTexCoord2.xyz); + v = vec3(gl_ModelViewMatrix * gl_Vertex); + } else { + mat4 bt = boneTransforms[int(gl_MultiTexCoord3[0])] * gl_MultiTexCoord4[0]; + bt += boneTransforms[int(gl_MultiTexCoord3[1])] * gl_MultiTexCoord4[1]; + bt += boneTransforms[int(gl_MultiTexCoord3[2])] * gl_MultiTexCoord4[2]; + bt += boneTransforms[int(gl_MultiTexCoord3[3])] * gl_MultiTexCoord4[3]; + + vec4 V = bt * gl_Vertex; + vec3 normal = vec3(bt * vec4(gl_Normal, 0.0)); + vec3 tan = vec3(bt * vec4(gl_MultiTexCoord1.xyz, 0.0)); + vec3 bit = vec3(bt * vec4(gl_MultiTexCoord2.xyz, 0.0)); + + gl_Position = gl_ModelViewProjectionMatrix * V; + N = normalize(gl_NormalMatrix * normal); + t = normalize(gl_NormalMatrix * tan); + b = normalize(gl_NormalMatrix * bit); + v = vec3(gl_ModelViewMatrix * V); + } mat3 tbnMatrix = mat3(b.x, t.x, N.x, b.y, t.y, N.y, b.z, t.z, N.z); - - v = vec3(gl_ModelViewMatrix * gl_Vertex); ViewDir = tbnMatrix * -v.xyz; LightDir = tbnMatrix * gl_LightSource[0].position.xyz; @@ -33,4 +53,4 @@ void main( void ) A = gl_LightSource[0].ambient; C = gl_Color; D = gl_LightSource[0].diffuse; -} \ No newline at end of file +} diff --git a/res/shaders/fo4_env.frag b/res/shaders/fo4_env.frag deleted file mode 100644 index 9d28d56f4..000000000 --- a/res/shaders/fo4_env.frag +++ /dev/null @@ -1,329 +0,0 @@ -#version 130 -#extension GL_ARB_shader_texture_lod : require - -uniform sampler2D BaseMap; -uniform sampler2D NormalMap; -//uniform sampler2D LightMask; -uniform sampler2D BacklightMap; -uniform sampler2D EnvironmentMap; -uniform sampler2D SpecularMap; -uniform sampler2D GreyscaleMap; -uniform samplerCube CubeMap; - -uniform vec3 specColor; -uniform float specStrength; -uniform float specGlossiness; // "Smoothness" in FO4; 0-1 -uniform float fresnelPower; - -uniform float paletteScale; - -uniform vec3 glowColor; -uniform float glowMult; - -uniform float alpha; - -uniform vec2 uvScale; -uniform vec2 uvOffset; - -uniform bool hasEmit; -uniform bool hasSoftlight; -uniform bool hasBacklight; -uniform bool hasRimlight; -uniform bool hasCubeMap; -uniform bool hasEnvMask; -uniform bool hasSpecularMap; -uniform bool greyscaleColor; -uniform bool doubleSided; - -uniform float subsurfaceRolloff; -uniform float rimPower; -uniform float backlightPower; - -uniform float envReflection; - -uniform mat4 worldMatrix; - -in vec3 LightDir; -in vec3 ViewDir; - -in vec4 A; -in vec4 C; -in vec4 D; - -in vec3 N; -in vec3 t; -in vec3 b; - -#ifndef M_PI - #define M_PI 3.1415926535897932384626433832795 -#endif - -#define FLT_EPSILON 1.192092896e-07F // smallest such that 1.0 + FLT_EPSILON != 1.0 - -float OrenNayar( vec3 L, vec3 V, vec3 N, float roughness, float NdotL ) -{ - //float NdotL = dot(N, L); - float NdotV = dot(N, V); - float LdotV = dot(L, V); - - float rough2 = roughness * roughness; - - float A = 1.0 - 0.5 * (rough2 / (rough2 + 0.57)); - float B = 0.45 * (rough2 / (rough2 + 0.09)); - - float a = min( NdotV, NdotL ); - float b = max( NdotV, NdotL ); - b = (sign(b) == 0.0) ? FLT_EPSILON : sign(b) * max( 0.01, abs(b) ); // For fudging the smoothness of C - float C = sqrt( (1.0 - a * a) * (1.0 - b * b) ) / b; - - float gamma = LdotV - NdotL * NdotV; - float L1 = A + B * max( gamma, FLT_EPSILON ) * C; - return L1 * max( NdotL, FLT_EPSILON ); -} - -float OrenNayarFull( vec3 L, vec3 V, vec3 N, float roughness, float NdotL ) -{ - //float NdotL = dot(N, L); - float NdotV = dot(N, V); - float LdotV = dot(L, V); - - float angleVN = acos(max(NdotV, FLT_EPSILON)); - float angleLN = acos(max(NdotL, FLT_EPSILON)); - - float alpha = max(angleVN, angleLN); - float beta = min(angleVN, angleLN); - float gamma = LdotV - NdotL * NdotV; - - float roughnessSquared = roughness * roughness; - float roughnessSquared9 = (roughnessSquared / (roughnessSquared + 0.09)); - - // C1, C2, and C3 - float C1 = 1.0 - 0.5 * (roughnessSquared / (roughnessSquared + 0.33)); - float C2 = 0.45 * roughnessSquared9; - - if( gamma >= 0.0 ) { - C2 *= sin(alpha); - } else { - C2 *= (sin(alpha) - pow((2.0 * beta) / M_PI, 3.0)); - } - - float powValue = (4.0 * alpha * beta) / (M_PI * M_PI); - float C3 = 0.125 * roughnessSquared9 * powValue * powValue; - - // Avoid asymptote at pi/2 - float asym = M_PI / 2.0; - float lim1 = asym + 0.01; - float lim2 = asym - 0.01; - - float ab2 = (alpha + beta) / 2.0; - - if ( beta >= asym && beta < lim1 ) - beta = lim1; - else if ( beta < asym && beta >= lim2 ) - beta = lim2; - - if ( ab2 >= asym && ab2 < lim1 ) - ab2 = lim1; - else if ( ab2 < asym && ab2 >= lim2 ) - ab2 = lim2; - - // Reflection - float A = gamma * C2 * tan(beta); - float B = (1.0 - abs(gamma)) * C3 * tan(ab2); - - float L1 = max(FLT_EPSILON, NdotL) * (C1 + A + B); - - // Interreflection - float twoBetaPi = 2.0 * beta / M_PI; - float L2 = 0.17 * max(FLT_EPSILON, NdotL) * (roughnessSquared / (roughnessSquared + 0.13)) * (1.0 - gamma * twoBetaPi * twoBetaPi); - - return L1 + L2; -} - -// Schlick's Fresnel approximation -float fresnelSchlick( float VdotH, float F0 ) -{ - float base = 1.0 - VdotH; - float exp = pow( base, fresnelPower ); - return clamp( exp + F0 * (1.0 - exp), 0.0, 1.0 ); -} - -// The Torrance-Sparrow visibility factor, G -float VisibDiv( float NdotL, float NdotV, float VdotH, float NdotH ) -{ - float denom = max( VdotH, FLT_EPSILON ); - float numL = min( NdotV, NdotL ); - float numR = 2.0 * NdotH; - if ( denom >= (numL * numR) ) { - numL = (numL == NdotV) ? 1.0 : (NdotL / NdotV); - return (numL * numR) / denom; - } - return 1.0 / NdotV; -} - -// this is a normalized Phong model used in the Torrance-Sparrow model -vec3 TorranceSparrow(float NdotL, float NdotH, float NdotV, float VdotH, vec3 color, float power, float F0) -{ - // D: Normalized phong model - float D = ((power + 2.0) / (2.0 * M_PI)) * pow( NdotH, power ); - - // G: Torrance-Sparrow visibility term divided by NdotV - float G_NdotV = VisibDiv( NdotL, NdotV, VdotH, NdotH ); - - // F: Schlick's approximation - float F = fresnelSchlick( VdotH, F0 ); - - // Torrance-Sparrow: - // (F * G * D) / (4 * NdotL * NdotV) - // Division by NdotV is done in VisibDiv() - // and division by NdotL is removed since - // outgoing radiance is determined by: - // BRDF * NdotL * L() - float spec = (F * G_NdotV * D) / 4.0; - - return color * spec * M_PI; -} - -vec3 tonemap(vec3 x) -{ - float _A = 0.15; - float _B = 0.50; - float _C = 0.10; - float _D = 0.20; - float _E = 0.02; - float _F = 0.30; - - return ((x*(_A*x+_C*_B)+_D*_E)/(x*(_A*x+_B)+_D*_F))-_E/_F; -} - -vec3 toGrayscale(vec3 color) -{ - return vec3(dot(vec3(0.3, 0.59, 0.11), color)); -} - -vec4 colorLookup( float x, float y ) { - - return texture2D( GreyscaleMap, vec2( clamp(x, 0.0, 1.0), clamp(y, 0.0, 1.0)) ); -} - -void main( void ) -{ - vec2 offset = gl_TexCoord[0].st * uvScale + uvOffset; - - vec4 baseMap = texture2D( BaseMap, offset ); - vec4 normalMap = texture2D( NormalMap, offset ); - vec4 specMap = texture2D( SpecularMap, offset ); - - vec3 normal = normalize(normalMap.rgb * 2.0 - 1.0); - // Calculate missing blue channel - normal.b = sqrt(1.0 - dot(normal.rg, normal.rg)); - if ( !gl_FrontFacing && doubleSided ) { - normal *= -1.0; - } - - vec3 L = normalize(LightDir); - vec3 V = normalize(ViewDir); - vec3 R = reflect(-L, normal); - vec3 H = normalize( L + V ); - - float NdotL = dot(normal, L); - float NdotL0 = max( NdotL, FLT_EPSILON ); - float NdotH = max( dot(normal, H), FLT_EPSILON ); - float NdotV = max( dot(normal, V), FLT_EPSILON ); - float VdotH = max( dot(V, H), FLT_EPSILON ); - float NdotNegL = max( dot(normal, -L), FLT_EPSILON ); - - vec3 reflected = reflect( V, normal ); - vec3 reflectedVS = b * reflected.x + t * reflected.y + N * reflected.z; - vec3 reflectedWS = vec3( worldMatrix * (gl_ModelViewMatrixInverse * vec4( reflectedVS, 0.0 )) ); - - - vec4 color; - vec3 albedo = baseMap.rgb * C.rgb; - vec3 diffuse = A.rgb + (D.rgb * NdotL0); - if ( greyscaleColor ) { - vec4 luG = colorLookup( baseMap.g, paletteScale - (1 - C.r) ); - - albedo = luG.rgb; - } - - // Emissive - vec3 emissive = vec3(0.0); - if ( hasEmit ) { - emissive += glowColor * glowMult; - } - - // Specular - float g = 1.0; - float s = 1.0; - float smoothness = clamp( specGlossiness, 0.0, 1.0 ); - float specMask = 1.0; - vec3 spec = vec3(0.0); - if ( hasSpecularMap ) { - g = specMap.g; - s = specMap.r; - smoothness = g * smoothness; - float fSpecularPower = exp2( smoothness * 10 + 1 ); - specMask = s * specStrength; - - spec = TorranceSparrow( NdotL0, NdotH, NdotV, VdotH, vec3(specMask), fSpecularPower, 0.2 ) * NdotL0 * D.rgb * specColor; - } - - // Environment - vec4 cube = textureLod( CubeMap, reflectedWS, 8.0 - smoothness * 8.0 ); - vec4 env = texture2D( EnvironmentMap, offset ); - if ( hasCubeMap ) { - cube.rgb *= envReflection * specStrength; - if ( hasEnvMask ) { - cube.rgb *= env.r; - } else { - cube.rgb *= s; - } - - spec += cube.rgb * diffuse; - } - - vec3 backlight = vec3(0.0); - if ( backlightPower > 0.0 ) { - backlight = albedo * NdotNegL * clamp( backlightPower, 0.0, 1.0 ); - - emissive += backlight * D.rgb; - } - - vec3 rim = vec3(0.0); - if ( hasRimlight ) { - rim = vec3(pow((1.0 - NdotV), rimPower)) * specMask; - rim *= smoothstep( -0.2, 1.0, dot(-L, V) ); - - //emissive += rim * D.rgb; - } - - // Diffuse - float diff = OrenNayarFull( L, V, normal, 1.0 - smoothness, NdotL ); - diffuse = vec3(diff); - - vec3 soft = vec3(0.0); - float wrap = NdotL; - if ( hasSoftlight || subsurfaceRolloff > 0.0 ) { - wrap = (wrap + subsurfaceRolloff) / (1.0 + subsurfaceRolloff); - soft = albedo * max( 0.0, wrap ) * smoothstep( 1.0, 0.0, sqrt(diff) ); - - diffuse += soft; - } - - // Diffuse - color.rgb = diffuse * albedo * D.rgb; - // Ambient - color.rgb += A.rgb * albedo; - // Specular - color.rgb += spec; - color.rgb += A.rgb * specMask * fresnelSchlick( VdotH, 0.2 ) * (1.0 - NdotV) * D.rgb; - // Emissive - color.rgb += emissive; - - color.rgb = tonemap( color.rgb ) / tonemap( vec3(1.0) ); - color.a = C.a * baseMap.a; - - gl_FragColor = color; - gl_FragColor.a *= alpha; -} diff --git a/res/shaders/fo4_env.prog b/res/shaders/fo4_env.prog deleted file mode 100644 index de2ffc6dc..000000000 --- a/res/shaders/fo4_env.prog +++ /dev/null @@ -1,30 +0,0 @@ -# environment - -checkgroup begin or - # Fallout 4 and later - checkgroup begin and - check HEADER/Version >= 0x14020007 - check HEADER/User Version >= 12 - check HEADER/User Version 2 >= 130 - checkgroup end -checkgroup end - -checkgroup begin and - check BSLightingShaderProperty - checkgroup begin or - check BSLightingShaderProperty/Skyrim Shader Type == 1 - check BSLightingShaderProperty/Skyrim Shader Type == 16 - checkgroup end - #check NiAVObject/Vertex Size >= 3 - checkgroup begin or - check BSTriShape - check BSSubIndexTriShape - check BSMeshLODTriShape - checkgroup end -checkgroup end - -texcoords 0 base -texcoords 1 tangents -texcoords 2 bitangents - -shaders fo4_env.vert fo4_env.frag diff --git a/res/shaders/fo4_env.vert b/res/shaders/fo4_env.vert deleted file mode 100644 index ed72d8219..000000000 --- a/res/shaders/fo4_env.vert +++ /dev/null @@ -1,37 +0,0 @@ -#version 130 - -out vec3 LightDir; -out vec3 ViewDir; - -out vec3 N; -out vec3 t; -out vec3 b; -out vec3 v; - -out vec4 A; -out vec4 C; -out vec4 D; - - -void main( void ) -{ - gl_Position = ftransform(); - gl_TexCoord[0] = gl_MultiTexCoord0; - - N = normalize(gl_NormalMatrix * gl_Normal); - t = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz); - b = normalize(gl_NormalMatrix * gl_MultiTexCoord2.xyz); - - mat3 tbnMatrix = mat3(b.x, t.x, N.x, - b.y, t.y, N.y, - b.z, t.z, N.z); - - v = vec3(gl_ModelViewMatrix * gl_Vertex); - - ViewDir = tbnMatrix * -v.xyz; - LightDir = tbnMatrix * gl_LightSource[0].position.xyz; - - A = gl_LightSource[0].ambient; - C = gl_Color; - D = gl_LightSource[0].diffuse; -} diff --git a/res/shaders/ob_glowmap.vert b/res/shaders/ob_glowmap.vert deleted file mode 100644 index 4d0212eff..000000000 --- a/res/shaders/ob_glowmap.vert +++ /dev/null @@ -1,37 +0,0 @@ -#version 120 - -varying vec3 LightDir; -varying vec3 ViewDir; -varying vec3 HalfVector; - -varying vec4 ColorEA; -varying vec4 ColorD; - -varying vec3 N; -varying vec3 t; -varying vec3 b; -varying vec3 v; - -void main( void ) -{ - gl_Position = ftransform(); - gl_TexCoord[0] = gl_MultiTexCoord0; - - N = normalize(gl_NormalMatrix * gl_Normal); - t = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz); - b = normalize(gl_NormalMatrix * gl_MultiTexCoord2.xyz); - - // NOTE: b<->t - mat3 tbnMatrix = mat3(b.x, t.x, N.x, - b.y, t.y, N.y, - b.z, t.z, N.z); - - v = vec3(gl_ModelViewMatrix * gl_Vertex); - - ViewDir = tbnMatrix * -v.xyz; - LightDir = tbnMatrix * gl_LightSource[0].position.xyz; - HalfVector = tbnMatrix * gl_LightSource[0].halfVector.xyz; - - ColorEA = gl_FrontMaterial.ambient * gl_LightSource[0].ambient; - ColorD = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse; -} diff --git a/res/shaders/ob_material_default.vert b/res/shaders/ob_material_default.vert deleted file mode 100644 index 58977d9b6..000000000 --- a/res/shaders/ob_material_default.vert +++ /dev/null @@ -1,43 +0,0 @@ -#version 120 - -varying vec3 LightDir; -varying vec3 ViewDir; -varying vec3 HalfVector; - -varying vec4 ColorEA; -varying vec4 ColorD; - -varying vec3 N; -varying vec3 t; -varying vec3 b; -varying vec3 v; - -varying vec4 A; -varying vec4 D; - -void main( void ) -{ - gl_Position = ftransform(); - gl_TexCoord[0] = gl_MultiTexCoord0; - - N = normalize(gl_NormalMatrix * gl_Normal); - t = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz); - b = normalize(gl_NormalMatrix * gl_MultiTexCoord2.xyz); - - // NOTE: b<->t - mat3 tbnMatrix = mat3(b.x, t.x, N.x, - b.y, t.y, N.y, - b.z, t.z, N.z); - - v = vec3(gl_ModelViewMatrix * gl_Vertex); - - ViewDir = tbnMatrix * -v.xyz; - LightDir = tbnMatrix * gl_LightSource[0].position.xyz; - HalfVector = tbnMatrix * gl_LightSource[0].halfVector.xyz; - - A = gl_LightSource[0].ambient; - D = gl_LightSource[0].diffuse; - - ColorEA = gl_FrontMaterial.emission + gl_FrontMaterial.ambient * A; - ColorD = gl_FrontMaterial.diffuse * D; -} diff --git a/res/shaders/ob_normal+glowmap.prog b/res/shaders/ob_normal+glowmap.prog deleted file mode 100644 index b046ee8eb..000000000 --- a/res/shaders/ob_normal+glowmap.prog +++ /dev/null @@ -1,28 +0,0 @@ -# normal + glow mapping, vertex colors -> ignore, material emissive color -> ignore - -checkgroup begin or - # Fallout 3 and later - checkgroup begin and - check HEADER/Version >= 0x14020007 - check HEADER/User Version >= 11 - checkgroup end - - checkgroup begin - check NiTexturingProperty/Apply Mode == 2 - check NiTriBasedGeom/Has Shader == 0 - checkgroup end -checkgroup end - -checkgroup begin or - checkgroup begin - check NiTriBasedGeomData/Has Vertex Colors == 0 - check NiVertexColorProperty/Vertex Mode == 1 - check NiVertexColorProperty/Lighting Mode == 1 - checkgroup end -checkgroup end - -texcoords 0 base -texcoords 1 tangents -texcoords 2 bitangents - -shaders ob_glowmap.vert ob_normalglowmap.frag diff --git a/res/shaders/ob_normal+glowmap_vcol.prog b/res/shaders/ob_normal+glowmap_vcol.prog deleted file mode 100644 index b046ee8eb..000000000 --- a/res/shaders/ob_normal+glowmap_vcol.prog +++ /dev/null @@ -1,28 +0,0 @@ -# normal + glow mapping, vertex colors -> ignore, material emissive color -> ignore - -checkgroup begin or - # Fallout 3 and later - checkgroup begin and - check HEADER/Version >= 0x14020007 - check HEADER/User Version >= 11 - checkgroup end - - checkgroup begin - check NiTexturingProperty/Apply Mode == 2 - check NiTriBasedGeom/Has Shader == 0 - checkgroup end -checkgroup end - -checkgroup begin or - checkgroup begin - check NiTriBasedGeomData/Has Vertex Colors == 0 - check NiVertexColorProperty/Vertex Mode == 1 - check NiVertexColorProperty/Lighting Mode == 1 - checkgroup end -checkgroup end - -texcoords 0 base -texcoords 1 tangents -texcoords 2 bitangents - -shaders ob_glowmap.vert ob_normalglowmap.frag diff --git a/res/shaders/ob_normalglowmap.frag b/res/shaders/ob_normalglowmap.frag deleted file mode 100644 index 5e72e0229..000000000 --- a/res/shaders/ob_normalglowmap.frag +++ /dev/null @@ -1,132 +0,0 @@ -#version 120 - -uniform sampler2D BaseMap; -uniform sampler2D NormalMap; -uniform sampler2D GlowMap; -uniform sampler2D LightMask; -uniform sampler2D BacklightMap; - -uniform bool hasGlowMap; -uniform vec3 glowColor; -uniform float glowMult; - -uniform vec3 specColor; -uniform float specStrength; -uniform float specGlossiness; - -uniform vec2 uvScale; -uniform vec2 uvOffset; - -uniform bool hasSoftlight; -uniform bool hasBacklight; -uniform bool hasRimlight; - -uniform float lightingEffect1; -uniform float lightingEffect2; - -varying vec3 LightDir; -varying vec3 ViewDir; - -varying vec4 ColorEA; -varying vec4 ColorD; - -vec3 tonemap(vec3 x) -{ - float A = 0.15; - float B = 0.50; - float C = 0.10; - float D = 0.20; - float E = 0.02; - float F = 0.30; - - return ((x*(A*x+C*B)+D*E)/(x*(A*x+B)+D*F))-E/F; -} - -void main( void ) -{ - vec2 offset = gl_TexCoord[0].st * uvScale + uvOffset; - - vec4 baseMap = texture2D( BaseMap, offset ); - vec4 nmap = texture2D( NormalMap, offset ); - - vec4 color; - color.rgb = baseMap.rgb; - - vec3 normal = normalize(nmap.rgb * 2.0 - 1.0); - - float spec = 0.0; - - vec3 emissive = texture2D( GlowMap, offset ).rgb; - - // Skyrim - if ( hasGlowMap ) { - color.rgb += tonemap( baseMap.rgb * emissive.rgb * glowColor ) / tonemap( 1.0f / (vec3(glowMult) + 0.001f) ); - } - - vec3 L = normalize(LightDir); - vec3 E = normalize(ViewDir); - vec3 R = reflect(-L, normal); - //vec3 H = normalize( L + E ); - float NdotL = max(dot(normal, L), 0.0); - - color.rgb *= ColorEA.rgb + ColorD.rgb * NdotL; - color.a = ColorD.a * baseMap.a; - - if ( NdotL > 0.0 && specStrength > 0.0 ) { - float RdotE = max( dot( R, E ), 0.0 ); - - // TODO: Attenuation? - - if ( RdotE > 0.0 ) { - spec = nmap.a * gl_LightSource[0].specular.r * specStrength * pow(RdotE, 0.8*specGlossiness); - color.rgb += spec * specColor; - } - } - - vec3 backlight; - if ( hasBacklight ) { - backlight = texture2D( BacklightMap, offset ).rgb; - color.rgb += baseMap.rgb * backlight * (1.0 - NdotL) * 0.66; - } - - vec4 mask; - if ( hasRimlight || hasSoftlight ) { - mask = texture2D( LightMask, offset ); - } - - float facing = dot(-L, E); - - vec3 rim; - if ( hasRimlight ) { - rim = vec3(( 1.0 - NdotL ) * ( 1.0 - max(dot(normal, E), 0.0))); - //rim = smoothstep( 0.0, 1.0, rim ); - rim = mask.rgb * pow(rim, vec3(lightingEffect2)) * gl_LightSource[0].diffuse.rgb * vec3(0.66); - rim *= smoothstep( -0.5, 1.0, facing ); - color.rgb += rim; - } - - float wrap = dot(normal, -L); - - vec3 soft; - if ( hasSoftlight ) { - soft = vec3((1.0 - wrap) * (1.0 - NdotL)); - soft = smoothstep( -1.0, 1.0, soft ); - - // TODO: Very approximate, kind of arbitrary. There is surely a more correct way. - soft *= mask.rgb * pow(soft, vec3(4.0/(lightingEffect1*lightingEffect1))); // * gl_LightSource[0].ambient.rgb; - //soft *= smoothstep( -1.0, 0.0, soft ); - //soft = mix( soft, color.rgb, gl_LightSource[0].ambient.rgb ); - - //soft = smoothstep( 0.0, 1.0, soft ); - soft *= gl_LightSource[0].diffuse.rgb * gl_LightSource[0].ambient.rgb + (0.01 * lightingEffect1*lightingEffect1); - - //soft = clamp( soft, 0.0, 0.5 ); - //soft *= smoothstep( -0.5, 1.0, facing ); - //soft = mix( soft, color.rgb, gl_LightSource[0].diffuse.rgb ); - color.rgb += baseMap.rgb * soft; - } - - //color = min( color, 1.0 ); - - gl_FragColor = color; -} diff --git a/res/shaders/ob_normalmap.frag b/res/shaders/ob_normalmap.frag deleted file mode 100644 index 94a55a8eb..000000000 --- a/res/shaders/ob_normalmap.frag +++ /dev/null @@ -1,129 +0,0 @@ -#version 120 - -uniform sampler2D BaseMap; -uniform sampler2D NormalMap; -uniform sampler2D LightMask; -uniform sampler2D BacklightMap; - -uniform vec3 specColor; -uniform float specStrength; -uniform float specGlossiness; - -uniform vec3 glowColor; -uniform float glowMult; - -uniform vec2 uvScale; -uniform vec2 uvOffset; - -uniform bool hasEmit; -uniform bool hasSoftlight; -uniform bool hasBacklight; -uniform bool hasRimlight; - -uniform float lightingEffect1; -uniform float lightingEffect2; - -varying vec3 LightDir; -varying vec3 ViewDir; - -varying vec4 ColorEA; -varying vec4 ColorD; - -varying vec4 A; -varying vec4 D; - - -vec3 tonemap(vec3 x) -{ - float _A = 0.15; - float _B = 0.50; - float _C = 0.10; - float _D = 0.20; - float _E = 0.02; - float _F = 0.30; - - return ((x*(_A*x+_C*_B)+_D*_E)/(x*(_A*x+_B)+_D*_F))-_E/_F; -} - -vec3 toGrayscale(vec3 color) -{ - return vec3(dot(vec3(0.3, 0.59, 0.11), color)); -} - -void main( void ) -{ - vec2 offset = gl_TexCoord[0].st * uvScale + uvOffset; - - vec4 baseMap = texture2D( BaseMap, offset ); - vec4 normalMap = texture2D( NormalMap, offset ); - - vec3 normal = normalize(normalMap.rgb * 2.0 - 1.0); - - - vec3 L = normalize(LightDir); - vec3 E = normalize(ViewDir); - vec3 R = reflect(-L, normal); - - float NdotL = max( dot(normal, L), 0.0 ); - float EdotN = max( dot(normal, E), 0.0 ); - float wrap = max( dot(normal, -L), 0.0 ); - float facing = max( dot(-L, E), 0.0 ); - - - vec4 color; - color.rgb = baseMap.rgb; - color.rgb *= ColorEA.rgb + (ColorD.rgb * NdotL); - color.a = ColorD.a * baseMap.a; - - - // Emissive - if ( hasEmit ) { - color.rgb += tonemap( baseMap.rgb * glowColor ) / tonemap( 1.0f / vec3(glowMult + 0.001f) ); - } - - // Specular - float spec = 0.0; - if ( NdotL > 0.0 && specStrength > 0.0 ) { - float RdotE = max( dot(R, E), 0.0 ); - if ( RdotE > 0.0 ) { - spec = normalMap.a * gl_LightSource[0].specular.r * specStrength * pow(RdotE, 0.8*specGlossiness); - color.rgb += spec * specColor; - } - } - - vec3 backlight; - if ( hasBacklight ) { - backlight = texture2D( BacklightMap, offset ).rgb; - color.rgb += baseMap.rgb * backlight * wrap * D.rgb; - } - - vec4 mask; - if ( hasRimlight || hasSoftlight ) { - mask = texture2D( LightMask, offset ); - } - - vec3 rim; - if ( hasRimlight ) { - rim = vec3((1.0 - NdotL) * (1.0 - EdotN)); - rim = mask.rgb * pow(rim, vec3(lightingEffect2)) * D.rgb * vec3(0.66); - rim *= smoothstep( -0.5, 1.0, facing ); - - color.rgb += rim; - } - - vec3 soft; - if ( hasSoftlight ) { - soft = vec3((1.0 - wrap) * (1.0 - NdotL)); - soft = smoothstep( -1.0, 1.0, soft ); - - // TODO: Very approximate, kind of arbitrary. There is surely a more correct way. - soft *= mask.rgb * pow(soft, vec3(4.0/(lightingEffect1*lightingEffect1))); - soft *= D.rgb * A.rgb + (0.01 * lightingEffect1*lightingEffect1); - - color.rgb += baseMap.rgb * soft; - } - - color.rgb = tonemap( color.rgb ) / tonemap( vec3(1.0) ); - - gl_FragColor = color; -} diff --git a/res/shaders/ob_normalmap.prog b/res/shaders/ob_normalmap.prog deleted file mode 100644 index 34317a3a8..000000000 --- a/res/shaders/ob_normalmap.prog +++ /dev/null @@ -1,33 +0,0 @@ -# normal mapping, no vertex colors - -checkgroup begin or - # Fallout 3 and later - checkgroup begin and - check HEADER/Version >= 0x14020007 - check HEADER/User Version >= 11 - checkgroup end - - checkgroup begin - check NiTexturingProperty/Apply Mode == 2 - check NiTriBasedGeom/Has Shader == 0 - checkgroup end -checkgroup end - -checkgroup begin or - # Fallout 3 - checkgroup begin and - check BSShaderPPLightingProperty - check NiTriBasedGeomData/Has Vertex Colors == 0 - checkgroup end - - checkgroup begin - check NiVertexColorProperty/Vertex Mode == 0 - check NiVertexColorProperty/Lighting Mode == 1 - checkgroup end -checkgroup end - -texcoords 0 base -texcoords 1 tangents -texcoords 2 bitangents - -shaders ob_material_default.vert ob_normalmap.frag diff --git a/res/shaders/ob_normalmap_vcol_ad.prog b/res/shaders/ob_normalmap_vcol_ad.prog deleted file mode 100644 index 5530fd8ee..000000000 --- a/res/shaders/ob_normalmap_vcol_ad.prog +++ /dev/null @@ -1,33 +0,0 @@ -# normal mapping, vertex colors -> ambient&diffuse - -checkgroup begin or - # Fallout 3 and later - checkgroup begin and - check HEADER/Version >= 0x14020007 - check HEADER/User Version >= 11 - checkgroup end - - checkgroup begin - check NiTexturingProperty/Apply Mode == 2 - check NiTriBasedGeom/Has Shader == 0 - checkgroup end -checkgroup end - -checkgroup begin or - # Fallout 3 - checkgroup begin and - check BSShaderPPLightingProperty - check NiTriBasedGeomData/Has Vertex Colors == 1 - checkgroup end - - checkgroup begin - check NiVertexColorProperty/Vertex Mode == 2 - check NiVertexColorProperty/Lighting Mode == 1 - checkgroup end -checkgroup end - -texcoords 0 base -texcoords 1 tangents -texcoords 2 bitangents - -shaders ob_vcolors_ad.vert ob_normalmap.frag diff --git a/res/shaders/ob_normalmap_vcol_e.prog b/res/shaders/ob_normalmap_vcol_e.prog deleted file mode 100644 index 20f37f1be..000000000 --- a/res/shaders/ob_normalmap_vcol_e.prog +++ /dev/null @@ -1,34 +0,0 @@ -# normal mapping, vertex colors -> emissive - -checkgroup begin or - # Fallout 3 and later - checkgroup begin and - check HEADER/Version >= 0x14020007 - check HEADER/User Version >= 11 - checkgroup end - - checkgroup begin - check NiTexturingProperty/Apply Mode == 2 - check NiTriBasedGeom/Has Shader == 0 - checkgroup end -checkgroup end - -checkgroup begin or - # Skyrim - //checkgroup begin and - // check BSEffectShaderProperty - // check NiTriBasedGeomData/Has Vertex Colors == 1 - //checkgroup end - - checkgroup begin - check NiTriBasedGeomData/Has Vertex Colors != 0 - check NiVertexColorProperty/Vertex Mode == 1 - check NiVertexColorProperty/Lighting Mode == 1 - checkgroup end -checkgroup end - -texcoords 0 base -texcoords 1 tangents -texcoords 2 bitangents - -shaders ob_vcolors_e.vert ob_normalmap.frag diff --git a/res/shaders/ob_parallax+glowmap.prog b/res/shaders/ob_parallax+glowmap.prog deleted file mode 100644 index 2622cf0b6..000000000 --- a/res/shaders/ob_parallax+glowmap.prog +++ /dev/null @@ -1,11 +0,0 @@ - -comment normal + glow mapping, vertex colors -> ignore, material emissive color -> ignore - -check NiTexturingProperty/Apply Mode == 4 -check NiTriBasedGeom/Has Shader == 0 - -texcoords 0 base -texcoords 1 tangents -texcoords 2 bitangents - -shaders ob_glowmap.vert ob_parallaxglowmap.frag diff --git a/res/shaders/ob_parallax.frag b/res/shaders/ob_parallax.frag deleted file mode 100644 index 10d80334b..000000000 --- a/res/shaders/ob_parallax.frag +++ /dev/null @@ -1,36 +0,0 @@ -#version 120 - -uniform sampler2D BaseMap; -uniform sampler2D NormalMap; - -varying vec3 LightDir; -varying vec3 HalfVector; -varying vec3 ViewDir; - -varying vec4 ColorEA; -varying vec4 ColorD; - -void main( void ) -{ - float offset = 0.015 - texture2D( BaseMap, gl_TexCoord[0].st ).a * 0.03; - vec2 texco = gl_TexCoord[0].st + normalize( ViewDir ).xy * offset; - - vec4 color = ColorEA; - - vec4 normal = texture2D( NormalMap, texco ); - normal.rgb = normal.rgb * 2.0 - 1.0; - - float NdotL = max( dot( normal.rgb, normalize( LightDir ) ), 0.0 ); - - if ( NdotL > 0.0 ) - { - color += ColorD * NdotL; - float NdotHV = max( dot( normal.rgb, normalize( HalfVector ) ), 0.0 ); - color += normal.a * gl_FrontMaterial.specular * gl_LightSource[0].specular * pow( NdotHV, gl_FrontMaterial.shininess ); - } - - color = min( color, 1.0 ); - color *= texture2D( BaseMap, texco ); - - gl_FragColor = color; -} diff --git a/res/shaders/ob_parallax.prog b/res/shaders/ob_parallax.prog deleted file mode 100644 index ba85a9a64..000000000 --- a/res/shaders/ob_parallax.prog +++ /dev/null @@ -1,19 +0,0 @@ - -comment normal+parallax mapping, no vertex colors - -check NiTexturingProperty/Apply Mode == 4 -check NiTriBasedGeom/Has Shader == 0 - -checkgroup begin or - check NiTriBasedGeomData/Has Vertex Colors == 0 - checkgroup begin - check NiVertexColorProperty/Vertex Mode == 0 - check NiVertexColorProperty/Lighting Mode == 1 - checkgroup end -checkgroup end - -texcoords 0 base -texcoords 1 tangents -texcoords 2 bitangents - -shaders ob_material_default.vert ob_parallax.frag diff --git a/res/shaders/ob_parallax_vcol_ad.prog b/res/shaders/ob_parallax_vcol_ad.prog deleted file mode 100644 index 7863ade8a..000000000 --- a/res/shaders/ob_parallax_vcol_ad.prog +++ /dev/null @@ -1,21 +0,0 @@ - -comment normal+parallax mapping, vertex colors -> ambient&diffuse - -check NiTexturingProperty/Apply Mode == 4 -check NiTriBasedGeom/Has Shader == 0 - -check NiTriBasedGeomData/Has Vertex Colors != 0 - -checkgroup begin or - check not NiVertexColorProperty - checkgroup begin - check NiVertexColorProperty/Vertex Mode == 2 - check NiVertexColorProperty/Lighting Mode == 1 - checkgroup end -checkgroup end - -texcoords 0 base -texcoords 1 tangents -texcoords 2 bitangents - -shaders ob_vcolors_ad.vert ob_parallax.frag diff --git a/res/shaders/ob_parallax_vcol_e.prog b/res/shaders/ob_parallax_vcol_e.prog deleted file mode 100644 index ba132839b..000000000 --- a/res/shaders/ob_parallax_vcol_e.prog +++ /dev/null @@ -1,17 +0,0 @@ - -comment normal+parallax mapping, vertex colors -> emissive - -check NiTexturingProperty/Apply Mode == 4 -check NiTriBasedGeom/Has Shader == 0 - -checkgroup begin - check NiTriBasedGeomData/Has Vertex Colors != 0 - check NiVertexColorProperty/Vertex Mode == 1 - check NiVertexColorProperty/Lighting Mode == 1 -checkgroup end - -texcoords 0 base -texcoords 1 tangents -texcoords 2 bitangents - -shaders ob_vcolors_e.vert ob_parallax.frag diff --git a/res/shaders/ob_parallaxglowmap.frag b/res/shaders/ob_parallaxglowmap.frag deleted file mode 100644 index 30b99e52c..000000000 --- a/res/shaders/ob_parallaxglowmap.frag +++ /dev/null @@ -1,38 +0,0 @@ -#version 120 - -uniform sampler2D BaseMap; -uniform sampler2D NormalMap; -uniform sampler2D GlowMap; - -varying vec3 LightDir; -varying vec3 HalfVector; -varying vec3 ViewDir; - -varying vec4 ColorEA; -varying vec4 ColorD; - -void main( void ) -{ - float offset = 0.015 - texture2D( BaseMap, gl_TexCoord[0].st ).a * 0.03; - vec2 texco = gl_TexCoord[0].st + normalize( ViewDir ).xy * offset; - - vec4 color = ColorEA; - color += texture2D( GlowMap, texco ); - - vec4 normal = texture2D( NormalMap, texco ); - normal.rgb = normal.rgb * 2.0 - 1.0; - - float NdotL = max( dot( normal.rgb, normalize( LightDir ) ), 0.0 ); - - if ( NdotL > 0.0 ) - { - color += ColorD * NdotL; - float NdotHV = max( dot( normal.rgb, normalize( HalfVector ) ), 0.0 ); - color += normal.a * gl_FrontMaterial.specular * gl_LightSource[0].specular * pow( NdotHV, gl_FrontMaterial.shininess ); - } - - color = min( color, 1.0 ); - color *= texture2D( BaseMap, texco ); - - gl_FragColor = color; -} diff --git a/res/shaders/ob_vcolors_ad.vert b/res/shaders/ob_vcolors_ad.vert deleted file mode 100644 index bee9676ed..000000000 --- a/res/shaders/ob_vcolors_ad.vert +++ /dev/null @@ -1,43 +0,0 @@ -#version 120 - -varying vec3 LightDir; -varying vec3 ViewDir; -varying vec3 HalfVector; - -varying vec4 ColorEA; -varying vec4 ColorD; - -varying vec3 N; -varying vec3 t; -varying vec3 b; -varying vec3 v; - -varying vec4 A; -varying vec4 D; - -void main( void ) -{ - gl_Position = ftransform(); - gl_TexCoord[0] = gl_MultiTexCoord0; - - N = normalize(gl_NormalMatrix * gl_Normal); - t = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz); - b = normalize(gl_NormalMatrix * gl_MultiTexCoord2.xyz); - - // NOTE: b<->t - mat3 tbnMatrix = mat3(b.x, t.x, N.x, - b.y, t.y, N.y, - b.z, t.z, N.z); - - v = vec3(gl_ModelViewMatrix * gl_Vertex); - - ViewDir = tbnMatrix * -v.xyz; - LightDir = tbnMatrix * gl_LightSource[0].position.xyz; - HalfVector = tbnMatrix * gl_LightSource[0].halfVector.xyz; - - A = gl_LightSource[0].ambient; - D = gl_LightSource[0].diffuse; - - ColorEA = gl_FrontMaterial.emission + gl_Color * A; - ColorD = gl_Color * D; -} diff --git a/res/shaders/ob_vcolors_e.vert b/res/shaders/ob_vcolors_e.vert deleted file mode 100644 index 45bdea3db..000000000 --- a/res/shaders/ob_vcolors_e.vert +++ /dev/null @@ -1,43 +0,0 @@ -#version 120 - -varying vec3 LightDir; -varying vec3 ViewDir; -varying vec3 HalfVector; - -varying vec4 ColorEA; -varying vec4 ColorD; - -varying vec3 N; -varying vec3 t; -varying vec3 b; -varying vec3 v; - -varying vec4 A; -varying vec4 D; - -void main( void ) -{ - gl_Position = ftransform(); - gl_TexCoord[0] = gl_MultiTexCoord0; - - N = normalize(gl_NormalMatrix * gl_Normal); - t = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz); - b = normalize(gl_NormalMatrix * gl_MultiTexCoord2.xyz); - - // NOTE: b<->t - mat3 tbnMatrix = mat3(b.x, t.x, N.x, - b.y, t.y, N.y, - b.z, t.z, N.z); - - v = vec3(gl_ModelViewMatrix * gl_Vertex); - - ViewDir = tbnMatrix * -v.xyz; - LightDir = tbnMatrix * gl_LightSource[0].position.xyz; - HalfVector = tbnMatrix * gl_LightSource[0].halfVector.xyz; - - A = gl_LightSource[0].ambient; - D = gl_LightSource[0].diffuse; - - ColorEA = gl_Color + gl_FrontMaterial.ambient * A; - ColorD = gl_FrontMaterial.diffuse * D; -} diff --git a/res/shaders/sk_default.frag b/res/shaders/sk_default.frag index 9167d7d2c..0f69d94bc 100644 --- a/res/shaders/sk_default.frag +++ b/res/shaders/sk_default.frag @@ -2,13 +2,18 @@ uniform sampler2D BaseMap; uniform sampler2D NormalMap; +uniform sampler2D GlowMap; +uniform sampler2D HeightMap; uniform sampler2D LightMask; uniform sampler2D BacklightMap; +uniform sampler2D EnvironmentMap; +uniform samplerCube CubeMap; uniform vec3 specColor; uniform float specStrength; uniform float specGlossiness; +uniform bool hasGlowMap; uniform vec3 glowColor; uniform float glowMult; @@ -16,6 +21,7 @@ uniform float alpha; uniform vec3 tintColor; +uniform bool hasHeightMap; uniform vec2 uvScale; uniform vec2 uvOffset; @@ -24,10 +30,16 @@ uniform bool hasSoftlight; uniform bool hasBacklight; uniform bool hasRimlight; uniform bool hasTintColor; +uniform bool hasCubeMap; +uniform bool hasEnvMask; uniform float lightingEffect1; uniform float lightingEffect2; +uniform float envReflection; + +uniform mat4 worldMatrix; + varying vec3 LightDir; varying vec3 ViewDir; @@ -35,6 +47,10 @@ varying vec4 A; varying vec4 C; varying vec4 D; +varying vec3 N; +varying vec3 t; +varying vec3 b; + vec3 tonemap(vec3 x) { @@ -57,14 +73,20 @@ void main( void ) { vec2 offset = gl_TexCoord[0].st * uvScale + uvOffset; + vec3 E = normalize(ViewDir); + + if ( hasHeightMap ) { + float height = texture2D( HeightMap, offset ).r; + offset += E.xy * (height * 0.08 - 0.04); + } + vec4 baseMap = texture2D( BaseMap, offset ); vec4 normalMap = texture2D( NormalMap, offset ); + vec4 glowMap = texture2D( GlowMap, offset ); vec3 normal = normalize(normalMap.rgb * 2.0 - 1.0); - vec3 L = normalize(LightDir); - vec3 E = normalize(ViewDir); vec3 R = reflect(-L, normal); vec3 H = normalize( L + E ); @@ -73,16 +95,40 @@ void main( void ) float EdotN = max( dot(normal, E), 0.0 ); float NdotNegL = max( dot(normal, -L), 0.0 ); + vec3 reflected = reflect( -E, normal ); + vec3 reflectedVS = b * reflected.x + t * reflected.y + N * reflected.z; + vec3 reflectedWS = vec3( worldMatrix * (gl_ModelViewMatrixInverse * vec4( reflectedVS, 0.0 )) ); + vec4 color; vec3 albedo = baseMap.rgb * C.rgb; vec3 diffuse = A.rgb + (D.rgb * NdotL); - // Emissive + // Environment + if ( hasCubeMap ) { + vec4 cube = textureCube( CubeMap, reflectedWS ); + cube.rgb *= envReflection; + + if ( hasEnvMask ) { + vec4 env = texture2D( EnvironmentMap, offset ); + cube.rgb *= env.r; + } else { + cube.rgb *= normalMap.a; + } + + + albedo += cube.rgb; + } + + // Emissive & Glow vec3 emissive = vec3(0.0); if ( hasEmit ) { emissive += glowColor * glowMult; + + if ( hasGlowMap ) { + emissive *= glowMap.rgb; + } } // Specular diff --git a/res/shaders/sk_default.prog b/res/shaders/sk_default.prog index 96cc8066e..8c86e072f 100644 --- a/res/shaders/sk_default.prog +++ b/res/shaders/sk_default.prog @@ -1,32 +1,25 @@ # default shader -checkgroup begin or - # Fallout 3 and later - checkgroup begin and - check HEADER/Version >= 0x14020007 - check HEADER/User Version >= 11 - checkgroup end -checkgroup end - -checkgroup begin or +checkgroup begin and # Skyrim - checkgroup begin and - check BSLightingShaderProperty - check BSLightingShaderProperty/Skyrim Shader Type != 1 - check BSLightingShaderProperty/Skyrim Shader Type != 2 - check BSLightingShaderProperty/Skyrim Shader Type != 3 - check BSLightingShaderProperty/Skyrim Shader Type != 11 - check BSLightingShaderProperty/Skyrim Shader Type != 16 - #check NiTriBasedGeomData/Has Vertex Colors == 1 - checkgroup begin or - check NiTriBasedGeomData/Has Normals == 1 - check BSTriShape/Vertex Desc & 8 - checkgroup end + check HEADER/User Version 2 >= 83 + check HEADER/User Version 2 <= 100 + check BSLightingShaderProperty + #check BSLightingShaderProperty/Skyrim Shader Type != 1 + #check BSLightingShaderProperty/Skyrim Shader Type != 2 + #check BSLightingShaderProperty/Skyrim Shader Type != 3 + #check BSLightingShaderProperty/Skyrim Shader Type != 11 + #check BSLightingShaderProperty/Skyrim Shader Type != 16 + checkgroup begin or + check NiTriBasedGeomData/Has Normals == 1 + check BSTriShape/Vertex Desc & 8 checkgroup end checkgroup end texcoords 0 base texcoords 1 tangents texcoords 2 bitangents +texcoords 3 indices +texcoords 4 weights shaders sk_default.vert sk_default.frag diff --git a/res/shaders/sk_default.vert b/res/shaders/sk_default.vert index a3aa1b308..54c4fef12 100644 --- a/res/shaders/sk_default.vert +++ b/res/shaders/sk_default.vert @@ -12,26 +12,44 @@ varying vec4 A; varying vec4 C; varying vec4 D; +uniform bool isGPUSkinned; +uniform mat4 boneTransforms[100]; void main( void ) { - gl_Position = ftransform(); + gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; gl_TexCoord[0] = gl_MultiTexCoord0; - N = normalize(gl_NormalMatrix * gl_Normal); - t = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz); - b = normalize(gl_NormalMatrix * gl_MultiTexCoord2.xyz); + if ( !isGPUSkinned ) { + N = normalize(gl_NormalMatrix * gl_Normal); + t = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz); + b = normalize(gl_NormalMatrix * gl_MultiTexCoord2.xyz); + v = vec3(gl_ModelViewMatrix * gl_Vertex); + } else { + mat4 bt = boneTransforms[int(gl_MultiTexCoord3[0])] * gl_MultiTexCoord4[0]; + bt += boneTransforms[int(gl_MultiTexCoord3[1])] * gl_MultiTexCoord4[1]; + bt += boneTransforms[int(gl_MultiTexCoord3[2])] * gl_MultiTexCoord4[2]; + bt += boneTransforms[int(gl_MultiTexCoord3[3])] * gl_MultiTexCoord4[3]; + + vec4 V = bt * gl_Vertex; + vec3 normal = vec3(bt * vec4(gl_Normal, 0.0)); + vec3 tan = vec3(bt * vec4(gl_MultiTexCoord1.xyz, 0.0)); + vec3 bit = vec3(bt * vec4(gl_MultiTexCoord2.xyz, 0.0)); + + gl_Position = gl_ModelViewProjectionMatrix * V; + N = normalize(gl_NormalMatrix * normal); + t = normalize(gl_NormalMatrix * tan); + b = normalize(gl_NormalMatrix * bit); + v = vec3(gl_ModelViewMatrix * V); + } - // NOTE: b<->t mat3 tbnMatrix = mat3(b.x, t.x, N.x, b.y, t.y, N.y, b.z, t.z, N.z); - - v = vec3(gl_ModelViewMatrix * gl_Vertex); ViewDir = tbnMatrix * -v.xyz; LightDir = tbnMatrix * gl_LightSource[0].position.xyz; - + A = gl_LightSource[0].ambient; C = gl_Color; D = gl_LightSource[0].diffuse; diff --git a/res/shaders/sk_effectshader.frag b/res/shaders/sk_effectshader.frag index 8f3ff5098..250c1a813 100644 --- a/res/shaders/sk_effectshader.frag +++ b/res/shaders/sk_effectshader.frag @@ -1,6 +1,6 @@ #version 120 -uniform sampler2D SourceTexture; +uniform sampler2D BaseMap; uniform sampler2D GreyscaleMap; uniform bool doubleSided; @@ -40,7 +40,7 @@ vec4 colorLookup( float x, float y ) { void main( void ) { - vec4 baseMap = texture2D( SourceTexture, gl_TexCoord[0].st * uvScale + uvOffset ); + vec4 baseMap = texture2D( BaseMap, gl_TexCoord[0].st * uvScale + uvOffset ); vec4 color; diff --git a/res/shaders/sk_effectshader.prog b/res/shaders/sk_effectshader.prog index 1c0155242..389b5422f 100644 --- a/res/shaders/sk_effectshader.prog +++ b/res/shaders/sk_effectshader.prog @@ -1,26 +1,16 @@ # normal mapping, vertex colors -> emissive -checkgroup begin or - # Fallout 3 and later - checkgroup begin and - check HEADER/Version >= 0x14020007 - check HEADER/User Version >= 11 - check HEADER/User Version 2 != 130 - checkgroup end - -checkgroup end - -checkgroup begin or +checkgroup begin and # Skyrim - checkgroup begin and - #check not NiAlphaProperty - check BSEffectShaderProperty - checkgroup end - + check HEADER/User Version 2 >= 83 + check HEADER/User Version 2 <= 100 + check BSEffectShaderProperty checkgroup end texcoords 0 base texcoords 1 tangents texcoords 2 bitangents +texcoords 3 indices +texcoords 4 weights shaders sk_effectshader.vert sk_effectshader.frag diff --git a/res/shaders/sk_effectshader.vert b/res/shaders/sk_effectshader.vert index afd493892..707391c4c 100644 --- a/res/shaders/sk_effectshader.vert +++ b/res/shaders/sk_effectshader.vert @@ -10,21 +10,40 @@ varying vec3 t; varying vec3 b; varying vec3 v; +uniform bool isGPUSkinned; +uniform mat4 boneTransforms[100]; + void main( void ) { - gl_Position = ftransform(); + gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; gl_TexCoord[0] = gl_MultiTexCoord0; - N = normalize(gl_NormalMatrix * gl_Normal); - t = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz); - b = normalize(gl_NormalMatrix * gl_MultiTexCoord2.xyz); + if ( !isGPUSkinned ) { + N = normalize(gl_NormalMatrix * gl_Normal); + t = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz); + b = normalize(gl_NormalMatrix * gl_MultiTexCoord2.xyz); + v = vec3(gl_ModelViewMatrix * gl_Vertex); + } else { + mat4 bt = boneTransforms[int(gl_MultiTexCoord3[0])] * gl_MultiTexCoord4[0]; + bt += boneTransforms[int(gl_MultiTexCoord3[1])] * gl_MultiTexCoord4[1]; + bt += boneTransforms[int(gl_MultiTexCoord3[2])] * gl_MultiTexCoord4[2]; + bt += boneTransforms[int(gl_MultiTexCoord3[3])] * gl_MultiTexCoord4[3]; + + vec4 V = bt * gl_Vertex; + vec3 normal = vec3(bt * vec4(gl_Normal, 0.0)); + vec3 tan = vec3(bt * vec4(gl_MultiTexCoord1.xyz, 0.0)); + vec3 bit = vec3(bt * vec4(gl_MultiTexCoord2.xyz, 0.0)); + + gl_Position = gl_ModelViewProjectionMatrix * V; + N = normalize(gl_NormalMatrix * normal); + t = normalize(gl_NormalMatrix * tan); + b = normalize(gl_NormalMatrix * bit); + v = vec3(gl_ModelViewMatrix * V); + } - // NOTE: b<->t mat3 tbnMatrix = mat3(b.x, t.x, N.x, b.y, t.y, N.y, b.z, t.z, N.z); - - v = vec3(gl_ModelViewMatrix * gl_Vertex); ViewDir = tbnMatrix * -v.xyz; LightDir = tbnMatrix * gl_LightSource[0].position.xyz; diff --git a/res/shaders/sk_env.frag b/res/shaders/sk_env.frag deleted file mode 100644 index b8df175df..000000000 --- a/res/shaders/sk_env.frag +++ /dev/null @@ -1,158 +0,0 @@ -#version 120 - -uniform sampler2D BaseMap; -uniform sampler2D NormalMap; -uniform sampler2D LightMask; -uniform sampler2D BacklightMap; -uniform sampler2D EnvironmentMap; -uniform samplerCube CubeMap; - -uniform vec3 specColor; -uniform float specStrength; -uniform float specGlossiness; - -uniform vec3 glowColor; -uniform float glowMult; - -uniform float alpha; - -uniform vec2 uvScale; -uniform vec2 uvOffset; - -uniform bool hasEmit; -uniform bool hasSoftlight; -uniform bool hasBacklight; -uniform bool hasRimlight; -uniform bool hasCubeMap; -uniform bool hasEnvMask; - -uniform float lightingEffect1; -uniform float lightingEffect2; - -uniform float envReflection; - -uniform mat4 worldMatrix; - -varying vec3 LightDir; -varying vec3 ViewDir; - -varying vec4 A; -varying vec4 C; -varying vec4 D; - -varying vec3 N; -varying vec3 t; -varying vec3 b; - - -vec3 tonemap(vec3 x) -{ - float _A = 0.15; - float _B = 0.50; - float _C = 0.10; - float _D = 0.20; - float _E = 0.02; - float _F = 0.30; - - return ((x*(_A*x+_C*_B)+_D*_E)/(x*(_A*x+_B)+_D*_F))-_E/_F; -} - -vec3 toGrayscale(vec3 color) -{ - return vec3(dot(vec3(0.3, 0.59, 0.11), color)); -} - -void main( void ) -{ - vec2 offset = gl_TexCoord[0].st * uvScale + uvOffset; - - vec4 baseMap = texture2D( BaseMap, offset ); - vec4 normalMap = texture2D( NormalMap, offset ); - - vec3 normal = normalize(normalMap.rgb * 2.0 - 1.0); - - - vec3 L = normalize(LightDir); - vec3 E = normalize(ViewDir); - vec3 R = reflect(-L, normal); - vec3 H = normalize( L + E ); - - float NdotL = max( dot(normal, L), 0.0 ); - float NdotH = max( dot(normal, H), 0.0 ); - float EdotN = max( dot(normal, E), 0.0 ); - float NdotNegL = max( dot(normal, -L), 0.0 ); - - vec3 reflected = reflect( -E, normal ); - vec3 reflectedVS = b * reflected.x + t * reflected.y + N * reflected.z; - vec3 reflectedWS = vec3( worldMatrix * (gl_ModelViewMatrixInverse * vec4( reflectedVS, 0.0 )) ); - - - vec4 color; - vec3 albedo = baseMap.rgb * C.rgb; - vec3 diffuse = A.rgb + (D.rgb * NdotL); - - - // Environment - if ( hasCubeMap ) { - vec4 cube = textureCube( CubeMap, reflectedWS ); - cube.rgb *= envReflection; - - if ( hasEnvMask ) { - vec4 env = texture2D( EnvironmentMap, offset ); - cube.rgb *= env.r; - } else { - cube.rgb *= normalMap.a; - } - - - albedo += cube.rgb; - } - - // Emissive - vec3 emissive = vec3(0.0); - if ( hasEmit ) { - emissive += glowColor * glowMult; - } - - // Specular - vec3 spec = clamp( specColor * specStrength * normalMap.a * pow(NdotH, specGlossiness), 0.0, 1.0 ); - spec *= D.rgb; - - vec3 backlight = vec3(0.0); - if ( hasBacklight ) { - backlight = texture2D( BacklightMap, offset ).rgb; - backlight *= NdotNegL; - - emissive += backlight * D.rgb; - } - - vec4 mask = vec4(0.0); - if ( hasRimlight || hasSoftlight ) { - mask = texture2D( LightMask, offset ); - } - - vec3 rim = vec3(0.0); - if ( hasRimlight ) { - rim = mask.rgb * pow(vec3((1.0 - EdotN)), vec3(lightingEffect2)); - rim *= smoothstep( -0.2, 1.0, dot(-L, E) ); - - emissive += rim * D.rgb; - } - - vec3 soft = vec3(0.0); - if ( hasSoftlight ) { - float wrap = (dot(normal, L) + lightingEffect1) / (1.0 + lightingEffect1); - - soft = max( wrap, 0.0 ) * mask.rgb * smoothstep( 1.0, 0.0, NdotL ); - soft *= sqrt( clamp( lightingEffect1, 0.0, 1.0 ) ); - - emissive += soft * D.rgb; - } - - color.rgb = albedo * (diffuse + emissive) + spec; - color.rgb = tonemap( color.rgb ) / tonemap( vec3(1.0) ); - color.a = C.a * baseMap.a; - - gl_FragColor = color; - gl_FragColor.a *= alpha; -} diff --git a/res/shaders/sk_env.prog b/res/shaders/sk_env.prog deleted file mode 100644 index f81123756..000000000 --- a/res/shaders/sk_env.prog +++ /dev/null @@ -1,30 +0,0 @@ -# multi-layer parallax - -checkgroup begin or - # Fallout 3 and later - checkgroup begin and - check HEADER/Version >= 0x14020007 - check HEADER/User Version >= 11 - checkgroup end -checkgroup end - -checkgroup begin or - # Skyrim - checkgroup begin and - check BSLightingShaderProperty - checkgroup begin or - check NiTriBasedGeomData/Has Normals == 1 - check BSTriShape/Vertex Desc & 8 - checkgroup end - checkgroup begin or - check BSLightingShaderProperty/Skyrim Shader Type == 1 - check BSLightingShaderProperty/Skyrim Shader Type == 16 - checkgroup end - checkgroup end -checkgroup end - -texcoords 0 base -texcoords 1 tangents -texcoords 2 bitangents - -shaders sk_env.vert sk_env.frag diff --git a/res/shaders/sk_env.vert b/res/shaders/sk_env.vert deleted file mode 100644 index a3aa1b308..000000000 --- a/res/shaders/sk_env.vert +++ /dev/null @@ -1,38 +0,0 @@ -#version 120 - -varying vec3 LightDir; -varying vec3 ViewDir; - -varying vec3 N; -varying vec3 t; -varying vec3 b; -varying vec3 v; - -varying vec4 A; -varying vec4 C; -varying vec4 D; - - -void main( void ) -{ - gl_Position = ftransform(); - gl_TexCoord[0] = gl_MultiTexCoord0; - - N = normalize(gl_NormalMatrix * gl_Normal); - t = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz); - b = normalize(gl_NormalMatrix * gl_MultiTexCoord2.xyz); - - // NOTE: b<->t - mat3 tbnMatrix = mat3(b.x, t.x, N.x, - b.y, t.y, N.y, - b.z, t.z, N.z); - - v = vec3(gl_ModelViewMatrix * gl_Vertex); - - ViewDir = tbnMatrix * -v.xyz; - LightDir = tbnMatrix * gl_LightSource[0].position.xyz; - - A = gl_LightSource[0].ambient; - C = gl_Color; - D = gl_LightSource[0].diffuse; -} diff --git a/res/shaders/sk_glowmap.frag b/res/shaders/sk_glowmap.frag deleted file mode 100644 index a342d22d4..000000000 --- a/res/shaders/sk_glowmap.frag +++ /dev/null @@ -1,133 +0,0 @@ -#version 120 - -uniform sampler2D BaseMap; -uniform sampler2D NormalMap; -uniform sampler2D GlowMap; -uniform sampler2D LightMask; -uniform sampler2D BacklightMap; - -uniform bool hasGlowMap; -uniform vec3 glowColor; -uniform float glowMult; - -uniform vec3 specColor; -uniform float specStrength; -uniform float specGlossiness; - -uniform float alpha; - -uniform vec2 uvScale; -uniform vec2 uvOffset; - -uniform bool hasEmit; -uniform bool hasSoftlight; -uniform bool hasBacklight; -uniform bool hasRimlight; - -uniform float lightingEffect1; -uniform float lightingEffect2; - -varying vec3 LightDir; -varying vec3 ViewDir; - -varying vec4 A; -varying vec4 C; -varying vec4 D; - - -vec3 tonemap(vec3 x) -{ - float _A = 0.15; - float _B = 0.50; - float _C = 0.10; - float _D = 0.20; - float _E = 0.02; - float _F = 0.30; - - return ((x*(_A*x+_C*_B)+_D*_E)/(x*(_A*x+_B)+_D*_F))-_E/_F; -} - -vec3 toGrayscale(vec3 color) -{ - return vec3(dot(vec3(0.3, 0.59, 0.11), color)); -} - -void main( void ) -{ - vec2 offset = gl_TexCoord[0].st * uvScale + uvOffset; - - vec4 baseMap = texture2D( BaseMap, offset ); - vec4 normalMap = texture2D( NormalMap, offset ); - vec4 glowMap = texture2D( GlowMap, offset ); - - vec3 normal = normalize(normalMap.rgb * 2.0 - 1.0); - - - vec3 L = normalize(LightDir); - vec3 E = normalize(ViewDir); - vec3 R = reflect(-L, normal); - vec3 H = normalize( L + E ); - - float NdotL = max( dot(normal, L), 0.0 ); - float NdotH = max( dot(normal, H), 0.0 ); - float EdotN = max( dot(normal, E), 0.0 ); - float NdotNegL = max( dot(normal, -L), 0.0 ); - - - vec4 color; - vec3 albedo = baseMap.rgb * C.rgb; - vec3 diffuse = A.rgb + (D.rgb * NdotL); - - - // Emissive & Glow - vec3 emissive = vec3(0.0); - if ( hasEmit ) { - emissive += glowColor * glowMult; - - if ( hasGlowMap ) { - emissive *= glowMap.rgb; - } - } - - // Specular - vec3 spec = clamp( specColor * specStrength * normalMap.a * pow(NdotH, specGlossiness), 0.0, 1.0 ); - spec *= D.rgb; - - vec3 backlight = vec3(0.0); - if ( hasBacklight ) { - backlight = texture2D( BacklightMap, offset ).rgb; - backlight *= NdotNegL; - - emissive += backlight * D.rgb; - } - - vec4 mask = vec4(0.0); - if ( hasRimlight || hasSoftlight ) { - mask = texture2D( LightMask, offset ); - } - - vec3 rim = vec3(0.0); - if ( hasRimlight ) { - rim = mask.rgb * pow(vec3((1.0 - EdotN)), vec3(lightingEffect2)); - rim *= smoothstep( -0.2, 1.0, dot(-L, E) ); - - emissive += rim * D.rgb; - } - - vec3 soft = vec3(0.0); - if ( hasSoftlight ) { - float wrap = (dot(normal, L) + lightingEffect1) / (1.0 + lightingEffect1); - - soft = max( wrap, 0.0 ) * mask.rgb * smoothstep( 1.0, 0.0, NdotL ); - soft *= sqrt( clamp( lightingEffect1, 0.0, 1.0 ) ); - - emissive += soft * D.rgb; - } - - color.rgb = albedo * (diffuse + emissive) + spec; - color.rgb = tonemap( color.rgb ) / tonemap( vec3(1.0) ); - color.a = C.a * baseMap.a; - - gl_FragColor = color; - gl_FragColor.a *= alpha; -} diff --git a/res/shaders/sk_glowmap.prog b/res/shaders/sk_glowmap.prog deleted file mode 100644 index 4011d5c32..000000000 --- a/res/shaders/sk_glowmap.prog +++ /dev/null @@ -1,27 +0,0 @@ -# glow mapping - -checkgroup begin or - # Fallout 3 and later - checkgroup begin and - check HEADER/Version >= 0x14020007 - check HEADER/User Version >= 11 - checkgroup end -checkgroup end - -checkgroup begin or - # Skyrim - checkgroup begin and - check BSLightingShaderProperty - check BSLightingShaderProperty/Skyrim Shader Type == 2 - checkgroup begin or - check NiTriBasedGeomData/Has Normals == 1 - check BSTriShape/Vertex Desc & 8 - checkgroup end - checkgroup end -checkgroup end - -texcoords 0 base -texcoords 1 tangents -texcoords 2 bitangents - -shaders sk_glowmap.vert sk_glowmap.frag diff --git a/res/shaders/sk_glowmap.vert b/res/shaders/sk_glowmap.vert deleted file mode 100644 index 88ee1720c..000000000 --- a/res/shaders/sk_glowmap.vert +++ /dev/null @@ -1,38 +0,0 @@ -#version 120 - -varying vec3 LightDir; -varying vec3 ViewDir; - -varying vec3 N; -varying vec3 t; -varying vec3 b; -varying vec3 v; - -varying vec4 A; -varying vec4 C; -varying vec4 D; - - -void main( void ) -{ - gl_Position = ftransform(); - gl_TexCoord[0] = gl_MultiTexCoord0; - - N = normalize(gl_NormalMatrix * gl_Normal); - t = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz); - b = normalize(gl_NormalMatrix * gl_MultiTexCoord2.xyz); - - // NOTE: b<->t - mat3 tbnMatrix = mat3(b.x, t.x, N.x, - b.y, t.y, N.y, - b.z, t.z, N.z); - - v = vec3(gl_ModelViewMatrix * gl_Vertex); - - ViewDir = tbnMatrix * -v.xyz; - LightDir = tbnMatrix * gl_LightSource[0].position.xyz; - - A = gl_LightSource[0].ambient; - C = gl_Color; - D = gl_LightSource[0].diffuse; -} diff --git a/res/shaders/sk_msn.frag b/res/shaders/sk_msn.frag index 2c6aca49a..d7ea9b8fc 100644 --- a/res/shaders/sk_msn.frag +++ b/res/shaders/sk_msn.frag @@ -36,7 +36,6 @@ uniform float lightingEffect1; uniform float lightingEffect2; uniform mat4 viewMatrix; -uniform mat4 viewMatrixInverse; varying vec3 v; diff --git a/res/shaders/sk_msn.prog b/res/shaders/sk_msn.prog index 2be16599f..b1fd18097 100644 --- a/res/shaders/sk_msn.prog +++ b/res/shaders/sk_msn.prog @@ -1,10 +1,10 @@ # model space normal mapping checkgroup begin or - # Fallout 3 and later checkgroup begin and - check HEADER/Version >= 0x14020007 - check HEADER/User Version >= 11 + # Skyrim + check HEADER/User Version 2 >= 83 + check HEADER/User Version 2 <= 100 checkgroup end checkgroup end @@ -24,7 +24,5 @@ checkgroup begin or checkgroup end texcoords 0 base -#texcoords 1 tangents -#texcoords 2 bitangents -shaders sk_msn.vert sk_msn.frag \ No newline at end of file +shaders sk_msn.vert sk_msn.frag diff --git a/res/shaders/sk_multilayer.prog b/res/shaders/sk_multilayer.prog index ec5d4b938..830a22a35 100644 --- a/res/shaders/sk_multilayer.prog +++ b/res/shaders/sk_multilayer.prog @@ -1,22 +1,14 @@ # multi-layer parallax -checkgroup begin or - # Fallout 3 and later - checkgroup begin and - check HEADER/Version >= 0x14020007 - check HEADER/User Version >= 11 - checkgroup end -checkgroup end - -checkgroup begin or +checkgroup begin and # Skyrim - checkgroup begin and - check BSLightingShaderProperty - check BSLightingShaderProperty/Skyrim Shader Type == 11 - checkgroup begin or - check NiTriBasedGeomData/Has Normals == 1 - check BSTriShape/Vertex Desc & 8 - checkgroup end + check HEADER/User Version 2 >= 83 + check HEADER/User Version 2 <= 100 + check BSLightingShaderProperty + check BSLightingShaderProperty/Skyrim Shader Type == 11 + checkgroup begin or + check NiTriBasedGeomData/Has Normals == 1 + check BSTriShape/Vertex Desc & 8 checkgroup end checkgroup end @@ -24,4 +16,4 @@ texcoords 0 base texcoords 1 tangents texcoords 2 bitangents -shaders sk_multilayer.vert sk_multilayer.frag +shaders sk_default.vert sk_multilayer.frag diff --git a/res/shaders/sk_multilayer.vert b/res/shaders/sk_multilayer.vert deleted file mode 100644 index 88ee1720c..000000000 --- a/res/shaders/sk_multilayer.vert +++ /dev/null @@ -1,38 +0,0 @@ -#version 120 - -varying vec3 LightDir; -varying vec3 ViewDir; - -varying vec3 N; -varying vec3 t; -varying vec3 b; -varying vec3 v; - -varying vec4 A; -varying vec4 C; -varying vec4 D; - - -void main( void ) -{ - gl_Position = ftransform(); - gl_TexCoord[0] = gl_MultiTexCoord0; - - N = normalize(gl_NormalMatrix * gl_Normal); - t = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz); - b = normalize(gl_NormalMatrix * gl_MultiTexCoord2.xyz); - - // NOTE: b<->t - mat3 tbnMatrix = mat3(b.x, t.x, N.x, - b.y, t.y, N.y, - b.z, t.z, N.z); - - v = vec3(gl_ModelViewMatrix * gl_Vertex); - - ViewDir = tbnMatrix * -v.xyz; - LightDir = tbnMatrix * gl_LightSource[0].position.xyz; - - A = gl_LightSource[0].ambient; - C = gl_Color; - D = gl_LightSource[0].diffuse; -} diff --git a/res/shaders/sk_parallax.frag b/res/shaders/sk_parallax.frag deleted file mode 100644 index 1435bd469..000000000 --- a/res/shaders/sk_parallax.frag +++ /dev/null @@ -1,130 +0,0 @@ -#version 120 - -uniform sampler2D BaseMap; -uniform sampler2D NormalMap; -uniform sampler2D HeightMap; -uniform sampler2D LightMask; -uniform sampler2D BacklightMap; - -uniform vec3 specColor; -uniform float specStrength; -uniform float specGlossiness; - -uniform vec3 glowColor; -uniform float glowMult; - -uniform float alpha; - -uniform vec2 uvScale; -uniform vec2 uvOffset; - -uniform bool hasEmit; -uniform bool hasSoftlight; -uniform bool hasBacklight; -uniform bool hasRimlight; - -uniform float lightingEffect1; -uniform float lightingEffect2; - -varying vec3 LightDir; -varying vec3 ViewDir; - -varying vec4 A; -varying vec4 C; -varying vec4 D; - - -vec3 tonemap(vec3 x) -{ - float _A = 0.15; - float _B = 0.50; - float _C = 0.10; - float _D = 0.20; - float _E = 0.02; - float _F = 0.30; - - return ((x*(_A*x+_C*_B)+_D*_E)/(x*(_A*x+_B)+_D*_F))-_E/_F; -} - -vec3 toGrayscale(vec3 color) -{ - return vec3(dot(vec3(0.3, 0.59, 0.11), color)); -} - -void main( void ) -{ - vec2 offset = gl_TexCoord[0].st * uvScale + uvOffset; - - vec3 E = normalize(ViewDir); - - float height = texture2D( HeightMap, offset ).r; - offset += E.xy * (height * 0.08 - 0.04); - - vec4 baseMap = texture2D( BaseMap, offset ); - vec4 normalMap = texture2D( NormalMap, offset ); - - vec3 normal = normalize(normalMap.rgb * 2.0 - 1.0); - - vec3 L = normalize(LightDir); - vec3 R = reflect(-L, normal); - vec3 H = normalize( L + E ); - - float NdotL = max( dot(normal, L), 0.0 ); - float NdotH = max( dot(normal, H), 0.0 ); - float EdotN = max( dot(normal, E), 0.0 ); - float NdotNegL = max( dot(normal, -L), 0.0 ); - - - vec4 color; - vec3 albedo = baseMap.rgb * C.rgb; - vec3 diffuse = A.rgb + (D.rgb * NdotL); - - - // Emissive - vec3 emissive = vec3(0.0); - if ( hasEmit ) { - emissive += glowColor * glowMult; - } - - // Specular - vec3 spec = clamp( specColor * specStrength * normalMap.a * pow(NdotH, specGlossiness), 0.0, 1.0 ); - spec *= D.rgb; - - vec3 backlight = vec3(0.0); - if ( hasBacklight ) { - backlight = texture2D( BacklightMap, offset ).rgb; - backlight *= NdotNegL; - - emissive += backlight * D.rgb; - } - - vec4 mask = vec4(0.0); - if ( hasRimlight || hasSoftlight ) { - mask = texture2D( LightMask, offset ); - } - - vec3 rim = vec3(0.0); - if ( hasRimlight ) { - rim = mask.rgb * pow(vec3((1.0 - EdotN)), vec3(lightingEffect2)); - rim *= smoothstep( -0.2, 1.0, dot(-L, E) ); - - emissive += rim * D.rgb; - } - - vec3 soft = vec3(0.0); - if ( hasSoftlight ) { - float wrap = (dot(normal, L) + lightingEffect1) / (1.0 + lightingEffect1); - - soft = max( wrap, 0.0 ) * mask.rgb * smoothstep( 1.0, 0.0, NdotL ); - soft *= sqrt( clamp( lightingEffect1, 0.0, 1.0 ) ); - - emissive += soft * D.rgb; - } - - color.rgb = albedo * (diffuse + emissive) + spec; - color.rgb = tonemap( color.rgb ) / tonemap( vec3(1.0) ); - color.a = C.a * baseMap.a; - - gl_FragColor = color; - gl_FragColor.a *= alpha; -} diff --git a/res/shaders/sk_parallax.prog b/res/shaders/sk_parallax.prog deleted file mode 100644 index 718a3af87..000000000 --- a/res/shaders/sk_parallax.prog +++ /dev/null @@ -1,27 +0,0 @@ -# default shader - -checkgroup begin or - # Fallout 3 and later - checkgroup begin and - check HEADER/Version >= 0x14020007 - check HEADER/User Version >= 11 - checkgroup end -checkgroup end - -checkgroup begin or - # Skyrim - checkgroup begin and - check BSLightingShaderProperty - check BSLightingShaderProperty/Skyrim Shader Type == 3 - checkgroup begin or - check NiTriBasedGeomData/Has Normals == 1 - check BSTriShape/Vertex Desc & 8 - checkgroup end - checkgroup end -checkgroup end - -texcoords 0 base -texcoords 1 tangents -texcoords 2 bitangents - -shaders sk_default.vert sk_parallax.frag diff --git a/src/gl/glmesh.cpp b/src/gl/glmesh.cpp index e7be9143b..4bfdeb6fe 100644 --- a/src/gl/glmesh.cpp +++ b/src/gl/glmesh.cpp @@ -116,6 +116,9 @@ void Shape::updateShaderProperties( const NifModel * nif ) QModelIndex iLSP = nif->getBlock( prop, "BSLightingShaderProperty" ); QModelIndex iESP = nif->getBlock( prop, "BSEffectShaderProperty" ); + // Reset stored shader so it can reassess conditions + shader = ""; + if ( iLSP.isValid() ) { if ( !bslsp ) bslsp = properties.get(); diff --git a/src/gl/glmesh.h b/src/gl/glmesh.h index 861a8943b..01c3b4d56 100644 --- a/src/gl/glmesh.h +++ b/src/gl/glmesh.h @@ -134,8 +134,8 @@ class Shape : public Node QVector weights; QVector partitions; - //! Holds the name of the shader, or "fixed function pipeline" if no shader - QString shader; + //! Holds the name of the shader, or "" if no shader + QString shader = ""; //! Shader property BSShaderLightingProperty * bssp = nullptr; diff --git a/src/gl/renderer.cpp b/src/gl/renderer.cpp index fa1b2f713..77e20c59e 100644 --- a/src/gl/renderer.cpp +++ b/src/gl/renderer.cpp @@ -327,13 +327,25 @@ bool Renderer::Program::load( const QString & filepath, Renderer * renderer ) if ( !ok || idStr.isEmpty() ) throw QString( "malformed texcoord tag" ); - if ( idStr != "tangents" && idStr != "bitangents" && TexturingProperty::getId( idStr ) < 0 ) - throw QString( "texcoord tag referres to unknown texture id '%1'" ).arg( idStr ); + int id = -1; + if ( idStr == "tangents" ) + id = CT_TANGENT; + else if ( idStr == "bitangents" ) + id = CT_BITANGENT; + else if ( idStr == "indices" ) + id = CT_BONE; + else if ( idStr == "weights" ) + id = CT_WEIGHT; + else if ( idStr == "base" ) + id = TexturingProperty::getId( idStr ); + + if ( id < 0 ) + throw QString( "texcoord tag refers to unknown texture id '%1'" ).arg( idStr ); if ( texcoords.contains( unit ) ) throw QString( "texture unit %1 is assigned twiced" ).arg( unit ); - texcoords.insert( unit, idStr ); + texcoords.insert( unit, CoordType(id) ); } } @@ -367,6 +379,12 @@ bool Renderer::Program::load( const QString & filepath, Renderer * renderer ) return true; } +void Renderer::Program::setUniformLocations() +{ + for ( int i = 0; i < NUM_UNIFORM_TYPES; i++ ) + uniformLocations[i] = f->glGetUniformLocation( id, uniforms[i].c_str() ); +} + Renderer::Renderer( QOpenGLContext * c, QOpenGLFunctions * f ) : cx( c ), fn( f ) { @@ -431,6 +449,7 @@ void Renderer::updateShaders() for ( const QString& name : dir.entryList() ) { Program * program = new Program( name, fn ); program->load( dir.filePath( name ), this ); + program->setUniformLocations(); programs.insert( name, program ); } } @@ -451,11 +470,12 @@ QString Renderer::setupProgram( Shape * mesh, const QString & hint ) PropertyList props; mesh->activeProperties( props ); - if ( !shader_ready || (mesh->scene->options & Scene::DisableShaders) + if ( !shader_ready || hint.isNull() + || (mesh->scene->options & Scene::DisableShaders) || (mesh->scene->visMode & Scene::VisSilhouette) || (mesh->nifVersion == 0) ) { setupFixedFunction( mesh, props ); - return QString( "fixed function pipeline" ); + return {}; } QVector iBlocks; @@ -467,9 +487,8 @@ QString Renderer::setupProgram( Shape * mesh, const QString & hint ) if ( !hint.isEmpty() ) { Program * program = programs.value( hint ); - - if ( program && program->status && setupProgram( program, mesh, props, iBlocks ) ) - return hint; + if ( program && program->status && setupProgram( program, mesh, props, iBlocks, false ) ) + return program->name; } for ( Program * program : programs ) { @@ -479,7 +498,7 @@ QString Renderer::setupProgram( Shape * mesh, const QString & hint ) stopProgram(); setupFixedFunction( mesh, props ); - return QString( "fixed function pipeline" ); + return {}; } void Renderer::stopProgram() @@ -491,6 +510,69 @@ void Renderer::stopProgram() resetTextureUnits(); } +void Renderer::Program::uni1f( UniformType var, float x ) +{ + f->glUniform1f( uniformLocations[var], x ); +} + +void Renderer::Program::uni2f( UniformType var, float x, float y ) +{ + f->glUniform2f( uniformLocations[var], x, y ); +} + +void Renderer::Program::uni3f( UniformType var, float x, float y, float z ) +{ + f->glUniform3f( uniformLocations[var], x, y, z ); +} + +void Renderer::Program::uni4f( UniformType var, float x, float y, float z, float w ) +{ + f->glUniform4f( uniformLocations[var], x, y, z, w ); +} + +void Renderer::Program::uni1i( UniformType var, int val ) +{ + f->glUniform1i( uniformLocations[var], val ); +} + +void Renderer::Program::uni3m( UniformType var, const Matrix & val ) +{ + if ( uniformLocations[var] >= 0 ) + f->glUniformMatrix3fv( uniformLocations[var], 1, 0, val.data() ); +} + +void Renderer::Program::uni4m( UniformType var, const Matrix4 & val ) +{ + if ( uniformLocations[var] >= 0 ) + f->glUniformMatrix4fv( uniformLocations[var], 1, 0, val.data() ); +} + +bool Renderer::Program::uniSampler( BSShaderLightingProperty * bsprop, UniformType var, + int textureSlot, int & texunit, const QString & alternate, uint clamp ) +{ + GLint uniSamp = uniformLocations[var]; + if ( uniSamp >= 0 ) { + + QString fname = bsprop->fileName( textureSlot ); + if ( fname.isEmpty() ) + fname = alternate; + + if ( !fname.isEmpty() && (!activateTextureUnit( texunit ) || !bsprop->bind( textureSlot, fname, TexClampMode(clamp) )) ) + return false; + + f->glUniform1i( uniSamp, texunit++ ); + + return true; + } + + return true; +} + +static QString white = "shaders/white.dds"; +static QString black = "shaders/black.dds"; +static QString gray = "shaders/gray.dds"; +static QString default_n = "shaders/default_n.dds"; + bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & props, const QVector & iBlocks, bool eval ) { @@ -516,7 +598,7 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & QString diff; if ( (opts & Scene::DoLighting) && (vis & Scene::VisNormalsOnly) ) - diff = "shaders/white.dds"; + diff = white; // texturing @@ -533,11 +615,7 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & int texunit = 0; - - //GLint baseWidth, baseHeight; - - GLint uniBaseMap = fn->glGetUniformLocation( prog->id, "BaseMap" ); - + GLint uniBaseMap = prog->uniformLocations[SAMP_BASE]; if ( uniBaseMap >= 0 ) { if ( !texprop && !bsprop ) return false; @@ -548,14 +626,10 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & if ( (texprop && !texprop->bind( 0 )) || (bsprop && !bsprop->bind( 0, diff, clamp )) ) return false; - //glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, (GLint *)&baseWidth ); - //glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, (GLint *)&baseHeight ); - fn->glUniform1i( uniBaseMap, texunit++ ); } - GLint uniNormalMap = fn->glGetUniformLocation( prog->id, "NormalMap" ); - + GLint uniNormalMap = prog->uniformLocations[SAMP_NORMAL]; if ( uniNormalMap >= 0 ) { if ( texprop ) { QString fname = texprop->fileName( 0 ); @@ -563,8 +637,7 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & if ( fname.isEmpty() ) return false; - int pos = fname.indexOf( "_" ); - + int pos = fname.lastIndexOf( "_" ); if ( pos >= 0 ) fname = fname.left( pos ) + "_n.dds"; else if ( (pos = fname.lastIndexOf( "." )) >= 0 ) @@ -576,7 +649,7 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & QString fname = bsprop->fileName( 1 ); if ( !(opts & Scene::DoLighting) ) - fname = "shaders/default_n.dds"; + fname = default_n; if ( !fname.isEmpty() && (!activateTextureUnit( texunit ) || !bsprop->bind( 1, fname, clamp )) ) return false; @@ -585,8 +658,7 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & fn->glUniform1i( uniNormalMap, texunit++ ); } - GLint uniGlowMap = fn->glGetUniformLocation( prog->id, "GlowMap" ); - + GLint uniGlowMap = prog->uniformLocations[SAMP_GLOW]; if ( uniGlowMap >= 0 ) { if ( texprop ) { QString fname = texprop->fileName( 0 ); @@ -594,8 +666,7 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & if ( fname.isEmpty() ) return false; - int pos = fname.indexOf( "_" ); - + int pos = fname.lastIndexOf( "_" ); if ( pos >= 0 ) fname = fname.left( pos ) + "_g.dds"; else if ( (pos = fname.lastIndexOf( "." )) >= 0 ) @@ -614,189 +685,107 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & } - // Sets a float - auto uni1f = [this, prog, mesh]( const char * var, float x ) { - GLint uni = fn->glGetUniformLocation( prog->id, var ); - if ( uni >= 0 ) - fn->glUniform1f( uni, x ); - }; - - // Sets a vec2 (two floats) - auto uni2f = [this, prog, mesh]( const char * var, float x, float y ) { - GLint uni = fn->glGetUniformLocation( prog->id, var ); - if ( uni >= 0 ) - fn->glUniform2f( uni, x, y ); - }; - - // Sets a vec3 (three floats) - auto uni3f = [this, prog, mesh]( const char * var, float x, float y, float z ) { - GLint uni = fn->glGetUniformLocation( prog->id, var ); - if ( uni >= 0 ) - fn->glUniform3f( uni, x, y, z ); - }; - - // Sets a vec4 (four floats) - auto uni4f = [this, prog, mesh]( const char * var, float x, float y, float z, float w ) { - GLint uni = fn->glGetUniformLocation( prog->id, var ); - if ( uni >= 0 ) - fn->glUniform4f( uni, x, y, z, w ); - }; - - // Sets an integer or boolean - auto uni1i = [this, prog, mesh]( const char * var, int val ) { - GLint uni = fn->glGetUniformLocation( prog->id, var ); - if ( uni >= 0 ) - fn->glUniform1i( uni, val ); - }; - - // Sets a mat3 (3x3 matrix) - auto uni3m = [this, prog, mesh]( const char * var, Matrix val ) { - GLint uni = fn->glGetUniformLocation( prog->id, var ); - if ( uni >= 0 ) { - fn->glUniformMatrix3fv( uni, 1, 0, val.data() ); - } - }; - - // Sets a mat4 (4x4 matrix) - auto uni4m = [this, prog, mesh]( const char * var, Matrix4 val ) { - GLint uni = fn->glGetUniformLocation( prog->id, var ); - if ( uni >= 0 ) { - fn->glUniformMatrix4fv( uni, 1, 0, val.data() ); - } - }; - - // Sets a sampler2D (texture sampler) - auto uniSampler = [this, prog, bsprop, &texunit]( const char * var, int textureSlot, QString alternate, TexClampMode clamp ) { - GLint uniSamp = fn->glGetUniformLocation( prog->id, var ); - if ( uniSamp >= 0 ) { - - QString fname = bsprop->fileName( textureSlot ); - if ( fname.isEmpty() ) - fname = alternate; - - if ( !fname.isEmpty() && (!activateTextureUnit( texunit ) || !bsprop->bind( textureSlot, fname, clamp )) ) - return false; - - fn->glUniform1i( uniSamp, texunit++ ); - - return true; - } - - return true; - }; - - QString white = "shaders/white.dds"; - QString black = "shaders/black.dds"; - QString default_n = "shaders/default_n.dds"; - - // BSLightingShaderProperty if ( mesh->bslsp ) { - uni1f( "lightingEffect1", mesh->bslsp->getLightingEffect1() ); - uni1f( "lightingEffect2", mesh->bslsp->getLightingEffect2() ); + prog->uni1f( LIGHT_EFF1, mesh->bslsp->getLightingEffect1() ); + prog->uni1f( LIGHT_EFF2, mesh->bslsp->getLightingEffect2() ); - uni1f( "alpha", mesh->bslsp->getAlpha() ); + prog->uni1f( ALPHA, mesh->bslsp->getAlpha() ); auto uvS = mesh->bslsp->getUvScale(); - uni2f( "uvScale", uvS.x, uvS.y ); + prog->uni2f( UV_SCALE, uvS.x, uvS.y ); auto uvO = mesh->bslsp->getUvOffset(); - uni2f( "uvOffset", uvO.x, uvO.y ); + prog->uni2f( UV_OFFSET, uvO.x, uvO.y ); - uni4m( "viewMatrix", mesh->viewTrans().toMatrix4() ); - uni4m( "viewMatrixInverse", mesh->viewTrans().toMatrix4().inverted() ); + prog->uni4m( MAT_VIEW, mesh->viewTrans().toMatrix4() ); + prog->uni4m( MAT_WORLD, mesh->worldTrans().toMatrix4() ); - uni4m( "localMatrix", mesh->localTrans().toMatrix4() ); - uni4m( "localMatrixInverse", mesh->localTrans().toMatrix4().inverted() ); - - uni4m( "worldMatrix", mesh->worldTrans().toMatrix4() ); - uni4m( "worldMatrixInverse", mesh->worldTrans().toMatrix4().inverted() ); - - uni1i( "greyscaleColor", mesh->bslsp->greyscaleColor ); + prog->uni1i( G2P_COLOR, mesh->bslsp->greyscaleColor ); if ( mesh->bslsp->greyscaleColor ) { - if ( !uniSampler( "GreyscaleMap", 3, "", TexClampMode::MIRRORED_S_MIRRORED_T ) ) + if ( !prog->uniSampler( bsprop, SAMP_GRAYSCALE, 3, texunit, "", TexClampMode::MIRRORED_S_MIRRORED_T ) ) return false; } - uni1i( "hasTintColor", mesh->bslsp->hasTintColor ); + prog->uni1i( HAS_TINT_COLOR, mesh->bslsp->hasTintColor ); if ( mesh->bslsp->hasTintColor ) { auto tC = mesh->bslsp->getTintColor(); - uni3f( "tintColor", tC.red(), tC.green(), tC.blue() ); + prog->uni3f( TINT_COLOR, tC.red(), tC.green(), tC.blue() ); } - uni1i( "hasDetailMask", mesh->bslsp->hasDetailMask ); + prog->uni1i( HAS_MAP_DETAIL, mesh->bslsp->hasDetailMask ); if ( mesh->bslsp->hasDetailMask ) { - if ( !uniSampler( "DetailMask", 3, "shaders/blankdetailmap.dds", clamp ) ) + if ( !prog->uniSampler( bsprop, SAMP_DETAIL, 3, texunit, "shaders/blankdetailmap.dds", clamp ) ) return false; } - uni1i( "hasTintMask", mesh->bslsp->hasTintMask ); + prog->uni1i( HAS_MAP_TINT, mesh->bslsp->hasTintMask ); if ( mesh->bslsp->hasTintMask ) { - if ( !uniSampler( "TintMask", 6, "shaders/gray.dds", clamp ) ) + if ( !prog->uniSampler( bsprop, SAMP_TINT, 6, texunit, gray, clamp ) ) return false; } // Rim & Soft params - uni1i( "hasSoftlight", mesh->bslsp->hasSoftlight ); - uni1i( "hasRimlight", mesh->bslsp->hasRimlight ); + prog->uni1i( HAS_SOFT, mesh->bslsp->hasSoftlight ); + prog->uni1i( HAS_RIM, mesh->bslsp->hasRimlight ); - if ( nif->getUserVersion2() < 130 && (mesh->bslsp->hasSoftlight || mesh->bslsp->hasRimlight) ) { + if ( mesh->nifVersion < 130 && (mesh->bslsp->hasSoftlight || mesh->bslsp->hasRimlight) ) { - if ( !uniSampler( "LightMask", 2, default_n, clamp ) ) + if ( !prog->uniSampler( bsprop, SAMP_LIGHT, 2, texunit, default_n, clamp ) ) return false; } // Backlight params - uni1i( "hasBacklight", mesh->bslsp->hasBacklight ); + prog->uni1i( HAS_MAP_BACK, mesh->bslsp->hasBacklight ); - if ( nif->getUserVersion2() < 130 && mesh->bslsp->hasBacklight ) { + if ( mesh->nifVersion < 130 && mesh->bslsp->hasBacklight ) { - if ( !uniSampler( "BacklightMap", 7, default_n, clamp ) ) + if ( !prog->uniSampler( bsprop, SAMP_BACKLIGHT, 7, texunit, default_n, clamp ) ) return false; } // Glow params if ( (opts & Scene::DoGlow) && (opts & Scene::DoLighting) && mesh->bslsp->hasEmittance ) - uni1f( "glowMult", mesh->bslsp->getEmissiveMult() ); + prog->uni1f( GLOW_MULT, mesh->bslsp->getEmissiveMult() ); else - uni1f( "glowMult", 0 ); + prog->uni1f( GLOW_MULT, 0 ); - uni1i( "hasEmit", mesh->bslsp->hasEmittance ); - uni1i( "hasGlowMap", mesh->bslsp->hasGlowMap ); + prog->uni1i( HAS_EMIT, mesh->bslsp->hasEmittance ); + prog->uni1i( HAS_MAP_GLOW, mesh->bslsp->hasGlowMap ); auto emC = mesh->bslsp->getEmissiveColor(); - uni3f( "glowColor", emC.red(), emC.green(), emC.blue() ); + prog->uni3f( GLOW_COLOR, emC.red(), emC.green(), emC.blue() ); // Specular params if ( (opts & Scene::DoSpecular) && (opts & Scene::DoLighting) ) - uni1f( "specStrength", mesh->bslsp->getSpecularStrength() ); + prog->uni1f( SPEC_SCALE, mesh->bslsp->getSpecularStrength() ); else - uni1f( "specStrength", 0 ); + prog->uni1f( SPEC_SCALE, 0 ); // Assure specular power does not break the shaders auto gloss = mesh->bslsp->getSpecularGloss(); - uni1f( "specGlossiness", gloss ); + prog->uni1f( SPEC_GLOSS, gloss ); auto spec = mesh->bslsp->getSpecularColor(); - uni3f( "specColor", spec.red(), spec.green(), spec.blue() ); + prog->uni3f( SPEC_COLOR, spec.red(), spec.green(), spec.blue() ); - uni1i( "hasSpecularMap", mesh->bslsp->hasSpecularMap ); + prog->uni1i( HAS_MAP_SPEC, mesh->bslsp->hasSpecularMap ); - if ( mesh->bslsp->hasSpecularMap && (nif->getUserVersion2() == 130 || !mesh->bslsp->hasBacklight) ) { - if ( !uniSampler( "SpecularMap", 7, white, clamp ) ) + if ( mesh->bslsp->hasSpecularMap && (mesh->nifVersion == 130 || !mesh->bslsp->hasBacklight) ) { + if ( !prog->uniSampler( bsprop, SAMP_SPECULAR, 7, texunit, white, clamp ) ) return false; } - if ( nif->getUserVersion2() == 130 ) { - uni1i( "doubleSided", mesh->bslsp->getIsDoubleSided() ); - uni1f( "paletteScale", mesh->bslsp->paletteScale ); - uni1f( "subsurfaceRolloff", mesh->bslsp->getLightingEffect1() ); - uni1f( "fresnelPower", mesh->bslsp->fresnelPower ); - uni1f( "rimPower", mesh->bslsp->rimPower ); - uni1f( "backlightPower", mesh->bslsp->backlightPower ); + if ( mesh->nifVersion == 130 ) { + prog->uni1i( DOUBLE_SIDE, mesh->bslsp->getIsDoubleSided() ); + prog->uni1f( G2P_SCALE, mesh->bslsp->paletteScale ); + prog->uni1f( SS_ROLLOFF, mesh->bslsp->getLightingEffect1() ); + prog->uni1f( POW_FRESNEL, mesh->bslsp->fresnelPower ); + prog->uni1f( POW_RIM, mesh->bslsp->rimPower ); + prog->uni1f( POW_BACK, mesh->bslsp->backlightPower ); } // Multi-Layer @@ -804,31 +793,31 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & if ( mesh->bslsp->hasMultiLayerParallax ) { auto inS = mesh->bslsp->getInnerTextureScale(); - uni2f( "innerScale", inS.x, inS.y ); + prog->uni2f( INNER_SCALE, inS.x, inS.y ); - uni1f( "innerThickness", mesh->bslsp->getInnerThickness() ); + prog->uni1f( INNER_THICK, mesh->bslsp->getInnerThickness() ); - uni1f( "outerRefraction", mesh->bslsp->getOuterRefractionStrength() ); - uni1f( "outerReflection", mesh->bslsp->getOuterReflectionStrength() ); + prog->uni1f( OUTER_REFR, mesh->bslsp->getOuterRefractionStrength() ); + prog->uni1f( OUTER_REFL, mesh->bslsp->getOuterReflectionStrength() ); - if ( !uniSampler( "InnerMap", 6, default_n, clamp ) ) + if ( !prog->uniSampler( bsprop, SAMP_INNER, 6, texunit, default_n, clamp ) ) return false; } // Environment Mapping - uni1i( "hasCubeMap", mesh->bslsp->hasCubeMap ); - uni1i( "hasEnvMask", mesh->bslsp->useEnvironmentMask ); + prog->uni1i( HAS_MAP_CUBE, mesh->bslsp->hasCubeMap ); + prog->uni1i( HAS_MASK_ENV, mesh->bslsp->useEnvironmentMask ); if ( mesh->bslsp->hasCubeMap && mesh->bslsp->hasEnvironmentMap && (opts & Scene::DoCubeMapping) && (opts & Scene::DoLighting) ) { - uni1f( "envReflection", mesh->bslsp->getEnvironmentReflection() ); + prog->uni1f( ENV_REFLECTION, mesh->bslsp->getEnvironmentReflection() ); // Do not test useEnvironmentMask here, always pass white.dds as fallback - if ( !uniSampler( "EnvironmentMap", 5, white, clamp ) ) + if ( !prog->uniSampler( bsprop, SAMP_ENV_MASK, 5, texunit, white, clamp ) ) return false; - GLint uniCubeMap = fn->glGetUniformLocation( prog->id, "CubeMap" ); + GLint uniCubeMap = prog->uniformLocations[SAMP_CUBE]; if ( uniCubeMap >= 0 ) { QString fname = bsprop->fileName( 4 ); @@ -842,13 +831,13 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & // In the case that the cube texture has already been bound, // but SLSF1_Environment_Mapping is not set, assure that it // removes reflections. - uni1f( "envReflection", 0 ); + prog->uni1f( ENV_REFLECTION, 0 ); } // Parallax - if ( mesh->bslsp->hasHeightMap ) { - if ( !uniSampler( "HeightMap", 3, "shaders/gray.dds", clamp ) ) + prog->uni1i( HAS_MAP_HEIGHT, true ); + if ( !prog->uniSampler( bsprop, SAMP_HEIGHT, 3, texunit, gray, clamp ) ) return false; } } @@ -857,71 +846,71 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & // BSEffectShaderProperty if ( mesh->bsesp ) { - uni4m( "worldMatrix", mesh->worldTrans().toMatrix4() ); + prog->uni4m( MAT_WORLD, mesh->worldTrans().toMatrix4() ); clamp = mesh->bsesp->getClampMode(); - if ( !uniSampler( "SourceTexture", 0, white, clamp ) ) + if ( !prog->uniSampler( bsprop, SAMP_BASE, 0, texunit, white, clamp ) ) return false; - uni1i( "doubleSided", mesh->bsesp->getIsDoubleSided() ); + prog->uni1i( DOUBLE_SIDE, mesh->bsesp->getIsDoubleSided() ); auto uvS = mesh->bsesp->getUvScale(); - uni2f( "uvScale", uvS.x, uvS.y ); + prog->uni2f( UV_SCALE, uvS.x, uvS.y ); auto uvO = mesh->bsesp->getUvOffset(); - uni2f( "uvOffset", uvO.x, uvO.y ); + prog->uni2f( UV_OFFSET, uvO.x, uvO.y ); - uni1i( "hasSourceTexture", mesh->bsesp->hasSourceTexture ); - uni1i( "hasGreyscaleMap", mesh->bsesp->hasGreyscaleMap ); + prog->uni1i( HAS_MAP_BASE, mesh->bsesp->hasSourceTexture ); + prog->uni1i( HAS_MAP_G2P, mesh->bsesp->hasGreyscaleMap ); - uni1i( "greyscaleAlpha", mesh->bsesp->greyscaleAlpha ); - uni1i( "greyscaleColor", mesh->bsesp->greyscaleColor ); + prog->uni1i( G2P_ALPHA, mesh->bsesp->greyscaleAlpha ); + prog->uni1i( G2P_COLOR, mesh->bsesp->greyscaleColor ); - uni1i( "useFalloff", mesh->bsesp->useFalloff ); - uni1i( "hasRGBFalloff", mesh->bsesp->hasRGBFalloff ); - uni1i( "hasWeaponBlood", mesh->bsesp->hasWeaponBlood ); + prog->uni1i( USE_FALLOFF, mesh->bsesp->useFalloff ); + prog->uni1i( HAS_RGBFALL, mesh->bsesp->hasRGBFalloff ); + prog->uni1i( HAS_WEAP_BLOOD, mesh->bsesp->hasWeaponBlood ); // Glow params auto emC = mesh->bsesp->getEmissiveColor(); - uni4f( "glowColor", emC.red(), emC.green(), emC.blue(), emC.alpha() ); - uni1f( "glowMult", mesh->bsesp->getEmissiveMult() ); + prog->uni4f( GLOW_COLOR, emC.red(), emC.green(), emC.blue(), emC.alpha() ); + prog->uni1f( GLOW_MULT, mesh->bsesp->getEmissiveMult() ); // Falloff params - uni4f( "falloffParams", + prog->uni4f( FALL_PARAMS, mesh->bsesp->falloff.startAngle, mesh->bsesp->falloff.stopAngle, mesh->bsesp->falloff.startOpacity, mesh->bsesp->falloff.stopOpacity ); - uni1f( "falloffDepth", mesh->bsesp->falloff.softDepth ); + prog->uni1f( FALL_DEPTH, mesh->bsesp->falloff.softDepth ); // BSEffectShader textures if ( mesh->bsesp->hasGreyscaleMap ) { - if ( !uniSampler( "GreyscaleMap", 1, "", TexClampMode::MIRRORED_S_MIRRORED_T ) ) + if ( !prog->uniSampler( bsprop, SAMP_GRAYSCALE, 1, texunit, "", TexClampMode::MIRRORED_S_MIRRORED_T ) ) return false; } - if ( nif->getUserVersion2() == 130 ) { + if ( mesh->nifVersion == 130 ) { - uni1f( "lightingInfluence", mesh->bsesp->getLightingInfluence() ); + prog->uni1f( LIGHT_INF, mesh->bsesp->getLightingInfluence() ); - uni1i( "hasNormalMap", mesh->bsesp->hasNormalMap && (opts & Scene::DoLighting) ); + prog->uni1i( HAS_MAP_NORMAL, mesh->bsesp->hasNormalMap && (opts & Scene::DoLighting) ); - uniSampler( "NormalMap", 3, default_n, clamp ); + prog->uniSampler( bsprop, SAMP_NORMAL, 3, texunit, default_n, clamp ); - uni1i( "hasCubeMap", mesh->bsesp->hasEnvMap ); - uni1i( "hasEnvMask", mesh->bsesp->hasEnvMask ); + prog->uni1i( HAS_MAP_CUBE, mesh->bsesp->hasEnvMap ); + prog->uni1i( HAS_MASK_ENV, mesh->bsesp->hasEnvMask ); if ( mesh->bsesp->hasEnvMap && (opts & Scene::DoCubeMapping) && (opts & Scene::DoLighting) ) { - uni1f( "envReflection", mesh->bsesp->getEnvironmentReflection() ); + prog->uni1f( ENV_REFLECTION, mesh->bsesp->getEnvironmentReflection() ); - if ( mesh->bsesp->hasEnvMask && !uniSampler( "SpecularMap", 4, white, clamp ) ) + if ( mesh->bsesp->hasEnvMask && !prog->uniSampler( bsprop, SAMP_SPECULAR, 4, texunit, white, clamp ) ) return false; - GLint uniCubeMap = fn->glGetUniformLocation( prog->id, "CubeMap" ); + GLint uniCubeMap = prog->uniformLocations[SAMP_CUBE]; if ( uniCubeMap >= 0 ) { QString fname = bsprop->fileName( 2 ); @@ -932,7 +921,7 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & fn->glUniform1i( uniCubeMap, texunit++ ); } } else { - uni1f( "envReflection", 0 ); + prog->uni1f( ENV_REFLECTION, 0 ); } } @@ -940,11 +929,11 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & // Defaults for uniforms in older meshes if ( !mesh->bsesp && !mesh->bslsp ) { - uni2f( "uvScale", 1.0, 1.0 ); - uni2f( "uvOffset", 0.0, 0.0 ); + prog->uni2f( UV_SCALE, 1.0, 1.0 ); + prog->uni2f( UV_OFFSET, 0.0, 0.0 ); } - QMapIterator itx( prog->texcoords ); + QMapIterator itx( prog->texcoords ); while ( itx.hasNext() ) { itx.next(); @@ -952,7 +941,8 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & if ( !activateTextureUnit( itx.key() ) ) return false; - if ( itx.value() == "tangents" ) { + auto it = itx.value(); + if ( it == Program::CT_TANGENT ) { if ( mesh->transTangents.count() ) { glEnableClientState( GL_TEXTURE_COORD_ARRAY ); glTexCoordPointer( 3, GL_FLOAT, 0, mesh->transTangents.constData() ); @@ -963,7 +953,7 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & return false; } - } else if ( itx.value() == "bitangents" ) { + } else if ( it == Program::CT_BITANGENT ) { if ( mesh->transBitangents.count() ) { glEnableClientState( GL_TEXTURE_COORD_ARRAY ); glTexCoordPointer( 3, GL_FLOAT, 0, mesh->transBitangents.constData() ); @@ -974,7 +964,7 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & return false; } } else if ( texprop ) { - int txid = TexturingProperty::getId( itx.value() ); + int txid = it; if ( txid < 0 ) return false; @@ -986,7 +976,7 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & glEnableClientState( GL_TEXTURE_COORD_ARRAY ); glTexCoordPointer( 2, GL_FLOAT, 0, mesh->coords[set].constData() ); } else if ( bsprop ) { - int txid = BSShaderLightingProperty::getId( itx.value() ); + int txid = it; if ( txid < 0 ) return false; @@ -1002,7 +992,7 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & // setup lighting - glEnable( GL_LIGHTING ); + //glEnable( GL_LIGHTING ); // setup blending @@ -1045,18 +1035,36 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & glAlphaFunc( GL_GREATER, 0.1f ); } - // setup vertex colors - - //glProperty( props.get< VertexColorProperty >(), glIsEnabled( GL_COLOR_ARRAY ) ); glDisable( GL_COLOR_MATERIAL ); - // setup material + if ( mesh->nifVersion < 83 ) { + // setup vertex colors - glProperty( props.get(), props.get() ); + //glProperty( props.get< VertexColorProperty >(), glIsEnabled( GL_COLOR_ARRAY ) ); + + // setup material - // setup z buffer + glProperty( props.get(), props.get() ); - glProperty( props.get() ); + // setup z buffer + + glProperty( props.get() ); + + // setup stencil + + glProperty( props.get() ); + + // wireframe ? + + glProperty( props.get() ); + } else { + glEnable( GL_DEPTH_TEST ); + glDepthMask( GL_TRUE ); + glDepthFunc( GL_LEQUAL ); + glEnable( GL_CULL_FACE ); + glCullFace( GL_BACK ); + + } if ( !mesh->depthTest ) { glDisable( GL_DEPTH_TEST ); @@ -1066,14 +1074,6 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & glDepthMask( GL_FALSE ); } - // setup stencil - - glProperty( props.get() ); - - // wireframe ? - - glProperty( props.get() ); - return true; } diff --git a/src/gl/renderer.h b/src/gl/renderer.h index 4042967f6..366b4843a 100644 --- a/src/gl/renderer.h +++ b/src/gl/renderer.h @@ -33,11 +33,16 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef GLSHADER_H #define GLSHADER_H +#include + #include #include #include #include +#include +#include + //! @file renderer.h Renderer, Renderer::ConditionSingle, Renderer::ConditionGroup, Renderer::Shader, Renderer::Program @@ -78,10 +83,80 @@ class Renderer : public QObject QOpenGLFunctions * fn; //! Set up shader program - QString setupProgram( Shape *, const QString & hint = QString() ); + QString setupProgram( Shape *, const QString & hint = {} ); //! Stop shader program void stopProgram(); + typedef enum + { + // Samplers + SAMP_BASE = 0, + SAMP_NORMAL, + SAMP_SPECULAR, + SAMP_CUBE, + SAMP_ENV_MASK, + SAMP_GLOW, + SAMP_HEIGHT, + SAMP_GRAYSCALE, + SAMP_DETAIL, + SAMP_TINT, + SAMP_LIGHT, + SAMP_BACKLIGHT, + SAMP_INNER, + // Uniforms + ALPHA, + DOUBLE_SIDE, + ENV_REFLECTION, + FALL_DEPTH, + FALL_PARAMS, + G2P_ALPHA, + G2P_COLOR, + G2P_SCALE, + GLOW_COLOR, + GLOW_MULT, + HAS_EMIT, + HAS_MAP_BACK, + HAS_MAP_BASE, + HAS_MAP_CUBE, + HAS_MAP_DETAIL, + HAS_MAP_G2P, + HAS_MAP_GLOW, + HAS_MAP_HEIGHT, + HAS_MAP_NORMAL, + HAS_MAP_SPEC, + HAS_MAP_TINT, + HAS_MASK_ENV, + HAS_RGBFALL, + HAS_RIM, + HAS_SOFT, + HAS_TINT_COLOR, + HAS_WEAP_BLOOD, + INNER_SCALE, + INNER_THICK, + LIGHT_EFF1, + LIGHT_EFF2, + LIGHT_INF, + MAT_VIEW, + MAT_WORLD, + OUTER_REFL, + OUTER_REFR, + POW_BACK, + POW_FRESNEL, + POW_RIM, + SPEC_COLOR, + SPEC_GLOSS, + SPEC_SCALE, + SS_ROLLOFF, + TINT_COLOR, + USE_FALLOFF, + UV_OFFSET, + UV_SCALE, + GPU_SKINNED, + GPU_BONES, + + NUM_UNIFORM_TYPES + } UniformType; + public slots: void updateSettings(); @@ -164,13 +239,111 @@ public slots: bool load( const QString & filepath, Renderer * ); + typedef enum + { + CT_BASE = 0, + CT_DARK, + CT_DETAIL, + CT_GLOSS, + CT_GLOW, + CT_BUMP, + CT_DECAL0, + CT_DECAL1, + CT_DECAL2, + CT_DECAL3, + CT_TANGENT, + CT_BITANGENT, + CT_BONE, + CT_WEIGHT + + } CoordType; + QOpenGLFunctions * f; QString name; GLuint id; bool status = false; ConditionGroup conditions; - QMap texcoords; + QMap texcoords; + + std::array uniforms = { { + "BaseMap", + "NormalMap", + "SpecularMap", + "CubeMap", + "EnvironmentMap", + "GlowMap", + "HeightMap", + "GreyscaleMap", + "DetailMask", + "TintMask", + "LightMask", + "BacklightMap", + "InnerMap", + "alpha", + "doubleSided", + "envReflection", + "falloffDepth", + "falloffParams", + "greyscaleAlpha", + "greyscaleColor", + "paletteScale", + "glowColor", + "glowMult", + "hasEmit", + "hasBacklight", + "hasSourceTexture", + "hasCubeMap", + "hasDetailMask", + "hasGreyscaleMap", + "hasGlowMap", + "hasHeightMap", + "hasNormalMap", + "hasSpecularMap", + "hasTintMask", + "hasEnvMask", + "hasRGBFalloff", + "hasRimlight", + "hasSoftlight", + "hasTintColor", + "hasWeaponBlood", + "innerScale", + "innerThickness", + "lightingEffect1", + "lightingEffect2", + "lightingInfluence", + "viewMatrix", + "worldMatrix", + "outerReflection", + "outerRefraction", + "backlightPower", + "fresnelPower", + "rimPower", + "specColor", + "specGlossiness", + "specStrength", + "subsurfaceRolloff", + "tintColor", + "useFalloff", + "uvOffset", + "uvScale", + "isGPUSkinned", + "boneTransforms" + } }; + + int uniformLocations[NUM_UNIFORM_TYPES]; + + void setUniformLocations(); + + void uni1f( UniformType var, float x ); + void uni2f( UniformType var, float x, float y ); + void uni3f( UniformType var, float x, float y, float z ); + void uni4f( UniformType var, float x, float y, float z, float w ); + void uni1i( UniformType var, int val ); + void uni3m( UniformType var, const Matrix & val ); + void uni4m( UniformType var, const Matrix4 & val ); + bool uniSampler( class BSShaderLightingProperty * bsprop, UniformType var, int textureSlot, + int & texunit, const QString & alternate, uint clamp ); }; QMap shaders; diff --git a/src/glview.cpp b/src/glview.cpp index 2d3108181..d44d8c0d1 100644 --- a/src/glview.cpp +++ b/src/glview.cpp @@ -554,7 +554,7 @@ void GLView::paintGL() glShadeModel( GL_SMOOTH ); - glEnable( GL_LIGHTING ); + //glEnable( GL_LIGHTING ); glEnable( GL_LIGHT0 ); glLightfv( GL_LIGHT0, GL_AMBIENT, mat_amb ); glLightfv( GL_LIGHT0, GL_DIFFUSE, mat_diff ); @@ -571,7 +571,7 @@ void GLView::paintGL() glShadeModel( GL_SMOOTH ); - glEnable( GL_LIGHTING ); + //glEnable( GL_LIGHTING ); glEnable( GL_LIGHT0 ); glLightfv( GL_LIGHT0, GL_AMBIENT, mat_amb ); glLightfv( GL_LIGHT0, GL_DIFFUSE, mat_diff ); @@ -583,7 +583,7 @@ void GLView::paintGL() GLfloat mat_amb[] = { 0.0f, 0.0f, 0.0f, 1.0f }; glShadeModel( GL_FLAT ); - glEnable( GL_LIGHTING ); + //glEnable( GL_LIGHTING ); glEnable( GL_LIGHT0 ); glLightModelfv( GL_LIGHT_MODEL_AMBIENT, mat_diff ); glMaterialfv( GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, mat_diff ); From c157d97929739bc5f5d75bed7f46d01dcffd744f Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sat, 16 Dec 2017 17:58:32 -0500 Subject: [PATCH 139/152] [UI] Value string changes Split the enum bitflag options after a user-defined number of options. Currently WIP. The combo box's line edit disables the newlines when the editor is created and reverts back to single line while the row stays the same height. Also show "undefined" when a bool value is 2, which is the undefined value for NiBool. --- src/data/nifvalue.cpp | 30 ++++++++++++++++++----- src/ui/settingsgeneral.ui | 50 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 6 deletions(-) diff --git a/src/data/nifvalue.cpp b/src/data/nifvalue.cpp index 99292a56d..c448bc99a 100644 --- a/src/data/nifvalue.cpp +++ b/src/data/nifvalue.cpp @@ -35,6 +35,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "model/nifmodel.h" #include +#include //! @file nifvalue.cpp NifValue @@ -44,6 +45,9 @@ QHash NifValue::typeTxt; QHash NifValue::enumMap; QHash NifValue::aliasMap; + +static int OPT_PER_LINE = -1; + /* * NifValue */ @@ -233,23 +237,34 @@ QString NifValue::enumOptionName( const QString & eid, quint32 val ) if ( eo.t == NifValue::eFlags ) { QString text; quint32 val2 = 0; - QMapIterator > it( eo.o ); - while ( it.hasNext() ) { - it.next(); + if ( OPT_PER_LINE == -1 ) { + QSettings settings; + OPT_PER_LINE = settings.value( "Settings/UI/Options Per Line" ).toInt(); + } + int opt = 0; + auto it = eo.o.constBegin(); + while ( it != eo.o.constEnd() ) { if ( val & ( 1 << it.key() ) ) { - val2 |= ( 1 << it.key() ); + val2 |= ( 1 << it.key() ); if ( !text.isEmpty() ) text += " | "; + if ( it != eo.o.constEnd() && opt != 0 && opt % OPT_PER_LINE == 0 ) + text += "\n"; + text += it.value().first; + + opt++; } + + it++; } + // Append any leftover value not covered by enums val2 = (val & ~val2); - if ( val2 ) { if ( !text.isEmpty() ) text += " | "; @@ -763,6 +778,9 @@ bool NifValue::setFromString( const QString & s ) } else if ( s == "no" || s == "false" ) { val.u32 = 0; return true; + } else if ( s == "undefined" ) { + val.u32 = 2; + return true; } case tByte: @@ -849,7 +867,7 @@ QString NifValue::toString() const { switch ( typ ) { case tBool: - return ( val.u32 ? "yes" : "no" ); + return ( (val.u32 == 2) ? "undefined" : (val.u32 ? "yes" : "no") ); case tByte: case tWord: case tFlags: diff --git a/src/ui/settingsgeneral.ui b/src/ui/settingsgeneral.ui index 7a4786cf1..1c27ee5cd 100644 --- a/src/ui/settingsgeneral.ui +++ b/src/ui/settingsgeneral.ui @@ -88,6 +88,56 @@ + + + + + 0 + 0 + + + + Block Details + + + true + + + + + + + 0 + 0 + + + + 2 + + + 32 + + + 3 + + + + + + + + 0 + 0 + + + + Flag options per line + + + + + + From 3b9a3bf97f6d0e0c0756ad378ad230a7fc4a6aab Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sat, 16 Dec 2017 17:59:29 -0500 Subject: [PATCH 140/152] [UI] Make Update Array part of the undo stack For the various additions made in dev7, sometimes undo/redo does not work well with the other undoable things if an Update Array command does not get sent to the array first. Such as lengthening an array, updating it, and then pasting another array onto it. --- src/model/nifmodel.h | 1 + src/model/undocommands.cpp | 23 +++++++++++++++++++++++ src/model/undocommands.h | 12 ++++++++++++ src/spellbook.cpp | 2 +- src/spells/misc.cpp | 3 ++- 5 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/model/nifmodel.h b/src/model/nifmodel.h index 4a59d69c0..86342809d 100644 --- a/src/model/nifmodel.h +++ b/src/model/nifmodel.h @@ -67,6 +67,7 @@ class NifModel final : public BaseModel friend class NifXmlHandler; friend class NifModelEval; friend class NifOStream; + friend class ArrayUpdateCommand; public: NifModel( QObject * parent = 0 ); diff --git a/src/model/undocommands.cpp b/src/model/undocommands.cpp index d49a0e445..4c894ce8f 100644 --- a/src/model/undocommands.cpp +++ b/src/model/undocommands.cpp @@ -185,3 +185,26 @@ void ToggleCheckBoxListCommand::undo() //qDebug() << nif->data( idx ).toString(); } + +ArrayUpdateCommand::ArrayUpdateCommand( const QModelIndex & index, NifModel * model ) + : QUndoCommand(), nif( model ), idx( index ) +{ + setText( QCoreApplication::translate( "ArrayUpdateCommand", "Update Array" ) ); +} + +void ArrayUpdateCommand::redo() +{ + if ( idx.isValid() ) { + oldSize = nif->rowCount( idx ); + nif->updateArray( idx ); + newSize = nif->rowCount( idx ); + } +} + +void ArrayUpdateCommand::undo() +{ + if ( idx.isValid() ) { + // TODO: Actually attempt to set the array size back + nif->updateArray( idx ); + } +} diff --git a/src/model/undocommands.h b/src/model/undocommands.h index a7eef3ff7..5bc8111f0 100644 --- a/src/model/undocommands.h +++ b/src/model/undocommands.h @@ -88,4 +88,16 @@ class ToggleCheckBoxListCommand : public QUndoCommand }; +class ArrayUpdateCommand : public QUndoCommand +{ +public: + ArrayUpdateCommand( const QModelIndex & index, NifModel * model ); + void redo() override; + void undo() override; +private: + NifModel * nif; + uint newSize, oldSize; + QPersistentModelIndex idx; +}; + #endif // UNDOCOMMANDS_H diff --git a/src/spellbook.cpp b/src/spellbook.cpp index 85617cded..3ec4c3b33 100644 --- a/src/spellbook.cpp +++ b/src/spellbook.cpp @@ -110,7 +110,7 @@ void SpellBook::cast( NifModel * nif, const QModelIndex & index, SpellPtr spell QDialogButtonBox::StandardButton response = QDialogButtonBox::Yes; - if ( !suppressConfirm ) { + if ( !suppressConfirm && spell->page() != "Array" ) { response = CheckableMessageBox::question( this, "Confirmation", "This action cannot currently be undone. Do you want to continue?", "Do not ask me again", &accepted ); if ( accepted ) diff --git a/src/spells/misc.cpp b/src/spells/misc.cpp index ed2814a3f..9ed5dd274 100644 --- a/src/spells/misc.cpp +++ b/src/spells/misc.cpp @@ -1,4 +1,5 @@ #include "misc.h" +#include "model/undocommands.h" #include @@ -50,7 +51,7 @@ class spUpdateArray final : public Spell QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final { - nif->updateArray( index ); + nif->undoStack->push( new ArrayUpdateCommand( index, nif ) ); return index; } }; From 947e7c00b99fe796ed556b221c5f303103bd8c27 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sat, 16 Dec 2017 18:03:05 -0500 Subject: [PATCH 141/152] [GL] Missing changes for 31fc1b2 and c0b44e2 --- src/gl/glmesh.cpp | 1 + src/gl/glproperty.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/gl/glmesh.cpp b/src/gl/glmesh.cpp index 4bfdeb6fe..856f09e48 100644 --- a/src/gl/glmesh.cpp +++ b/src/gl/glmesh.cpp @@ -936,6 +936,7 @@ void Mesh::transformShapes() transNorms = norms; transTangents = tangents; transBitangents = bitangents; + transColors = colors; } sortedTriangles = triangles; diff --git a/src/gl/glproperty.cpp b/src/gl/glproperty.cpp index bf45710bb..1fd81254a 100644 --- a/src/gl/glproperty.cpp +++ b/src/gl/glproperty.cpp @@ -1185,8 +1185,8 @@ void BSLightingShaderProperty::updateParams( const NifModel * nif, const QModelI setEmissive( nif->get( prop, "Emissive Color" ), nif->get( prop, "Emissive Multiple" ) ); hasEmittance = hasSF1( ShaderFlags::SLSF1_Own_Emit ); - if ( getShaderType() & ShaderFlags::ST_GlowShader ) - hasGlowMap = hasSF2( ShaderFlags::SLSF2_Glow_Map ) && !textures.value( 2, "" ).isEmpty(); + hasGlowMap = getShaderType() & ShaderFlags::ST_GlowShader && hasSF2( ShaderFlags::SLSF2_Glow_Map ) && !textures.value( 2, "" ).isEmpty(); + // Version Dependent settings if ( stream < 130 ) { From 4c8fbf58e4e580bc5141055898257c8342824a57 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sat, 16 Dec 2017 18:22:46 -0500 Subject: [PATCH 142/152] [UI] Small buddy() refactor Use the actual buddy() method for QAbstractItemModel --- src/model/nifmodel.cpp | 106 ++++++++++++++++++----------------------- src/model/nifmodel.h | 2 + 2 files changed, 49 insertions(+), 59 deletions(-) diff --git a/src/model/nifmodel.cpp b/src/model/nifmodel.cpp index 122d96714..caf04c4de 100644 --- a/src/model/nifmodel.cpp +++ b/src/model/nifmodel.cpp @@ -1171,6 +1171,8 @@ bool NifModel::setItemValue( NifItem * item, const NifValue & val ) QVariant NifModel::data( const QModelIndex & idx, int role ) const { QModelIndex index = buddy( idx ); + if ( index != idx ) + return data( index, role ); NifItem * item = static_cast( index.internalPointer() ); @@ -1184,48 +1186,6 @@ QVariant NifModel::data( const QModelIndex & idx, int role ) const if ( role == NifSkopeDisplayRole ) role = Qt::DisplayRole; - if ( column == ValueCol && item->parent() == root && item->type() == "NiBlock" ) { - QModelIndex buddy; - - if ( item->name() == "NiSourceTexture" || item->name() == "NiImage" ) { - buddy = getIndex( index, "File Name" ); - } else if ( item->name() == "NiStringExtraData" ) { - buddy = getIndex( index, "String Data" ); - //else if ( item->name() == "NiTransformInterpolator" && role == Qt::DisplayRole) - // return QString(tr("TODO: find out who is referring me")); - } else { - buddy = getIndex( index, "Name" ); - } - - if ( buddy.isValid() ) - buddy = buddy.sibling( buddy.row(), index.column() ); - - if ( buddy.isValid() ) - return data( buddy, role ); - } else if ( column == ValueCol && item->parent() != root && item->type() == "ControllerLink" && role == Qt::DisplayRole ) { - QModelIndex buddy; - - if ( item->name() == "Controlled Blocks" ) { - if ( version >= 0x14010003 ) { - buddy = getIndex( index, "Node Name" ); - - if ( buddy.isValid() ) - buddy = buddy.sibling( buddy.row(), index.column() ); - - if ( buddy.isValid() ) - return data( buddy, role ); - } else if ( version <= 0x14000005 ) { - buddy = getIndex( index, "Node Name Offset" ); - - if ( buddy.isValid() ) - buddy = buddy.sibling( buddy.row(), index.column() ); - - if ( buddy.isValid() ) - return data( buddy, role ); - } - } - } - switch ( role ) { case Qt::DisplayRole: { @@ -1570,23 +1530,10 @@ bool NifModel::setData( const QModelIndex & index, const QVariant & value, int r if ( !( index.isValid() && role == Qt::EditRole && index.model() == this && item ) ) return false; - // buddy lookup - if ( index.column() == ValueCol && item->parent() == root && item->type() == "NiBlock" ) { - QModelIndex buddy; - - if ( item->name() == "NiSourceTexture" || item->name() == "NiImage" ) - buddy = getIndex( index, "File Name" ); - else if ( item->name() == "NiStringExtraData" ) - buddy = getIndex( index, "String Data" ); - else - buddy = getIndex( index, "Name" ); - - if ( buddy.isValid() ) - buddy = buddy.sibling( buddy.row(), index.column() ); - - if ( buddy.isValid() ) - return setData( buddy, value, role ); - } + // Set Buddy + QModelIndex idx = buddy( index ); + if ( index != idx ) + return setData( idx, value, role ); switch ( index.column() ) { case NifModel::NameCol: @@ -1707,6 +1654,47 @@ bool NifModel::removeRows( int row, int count, const QModelIndex & parent ) return false; } +QModelIndex NifModel::buddy( const QModelIndex & index ) const +{ + NifItem * item = static_cast(index.internalPointer()); + if ( !item ) + return QModelIndex(); + + QModelIndex buddy; + if ( index.column() == ValueCol && item->parent() == root && item->type() == "NiBlock" ) { + + if ( item->name() == "NiSourceTexture" || item->name() == "NiImage" ) { + buddy = getIndex( index, "File Name" ); + } else if ( item->name() == "NiStringExtraData" ) { + buddy = getIndex( index, "String Data" ); + } else { + buddy = getIndex( index, "Name" ); + } + + if ( buddy.isValid() ) + buddy = buddy.sibling( buddy.row(), ValueCol ); + + if ( buddy.isValid() ) + return buddy; + } else if ( index.column() == ValueCol && item->parent() != root ) { + + if ( item->type() == "ControlledBlock" && item->name() == "Controlled Blocks" ) { + if ( version >= 0x14010003 ) { + buddy = getIndex( index, "Node Name" ); + } else if ( version <= 0x14000005 ) { + buddy = getIndex( index, "Node Name Offset" ); + } + + if ( buddy.isValid() ) + buddy = buddy.sibling( buddy.row(), ValueCol ); + + return buddy; + } + } + + return index; +} + /* * load and save diff --git a/src/model/nifmodel.h b/src/model/nifmodel.h index 86342809d..41d777f4b 100644 --- a/src/model/nifmodel.h +++ b/src/model/nifmodel.h @@ -84,6 +84,8 @@ class NifModel final : public BaseModel bool setData( const QModelIndex & index, const QVariant & value, int role = Qt::EditRole ) override final; bool removeRows( int row, int count, const QModelIndex & parent ) override final; + QModelIndex buddy( const QModelIndex & index ) const override; + // end QAbstractItemModel // BaseModel From e742ee56da4b6a7ef522fc34ad7f94d8b2df76d8 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sat, 16 Dec 2017 18:25:48 -0500 Subject: [PATCH 143/152] [UI] Support more transforms for spells etc. In addition to "Skin Transform" support transforms named "Transform". --- src/data/niftypes.cpp | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/data/niftypes.cpp b/src/data/niftypes.cpp index 2d8a76fa4..61f7e59ce 100644 --- a/src/data/niftypes.cpp +++ b/src/data/niftypes.cpp @@ -665,28 +665,30 @@ bool Transform::canConstruct( const NifModel * nif, const QModelIndex & parent ) Transform::Transform( const NifModel * nif, const QModelIndex & transform ) { - QModelIndex skinTransform = nif->getIndex( transform, "Skin Transform" ); - - if ( !skinTransform.isValid() ) { - skinTransform = transform; + QModelIndex t = nif->getIndex( transform, "Transform" ); + if ( !t.isValid() ) { + t = nif->getIndex( transform, "Skin Transform" ); + if ( !t.isValid() ) + t = transform; } - rotation = nif->get( skinTransform, "Rotation" ); - translation = nif->get( skinTransform, "Translation" ); - scale = nif->get( skinTransform, "Scale" ); + rotation = nif->get( t, "Rotation" ); + translation = nif->get( t, "Translation" ); + scale = nif->get( t, "Scale" ); } void Transform::writeBack( NifModel * nif, const QModelIndex & transform ) const { - QModelIndex skinTransform = nif->getIndex( transform, "Skin Transform" ); - - if ( !skinTransform.isValid() ) { - skinTransform = transform; + QModelIndex t = nif->getIndex( transform, "Transform" ); + if ( !t.isValid() ) { + t = nif->getIndex( transform, "Skin Transform" ); + if ( !t.isValid() ) + t = transform; } - nif->set( skinTransform, "Rotation", rotation ); - nif->set( skinTransform, "Translation", translation ); - nif->set( skinTransform, "Scale", scale ); + nif->set( t, "Rotation", rotation ); + nif->set( t, "Translation", translation ); + nif->set( t, "Scale", scale ); } QString Transform::toString() const From 7bdf5c83ec61c471878c4ea5143203052d01c49a Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sat, 16 Dec 2017 18:55:26 -0500 Subject: [PATCH 144/152] [NifModel] Minor memory and speed improvements Cut down NifItem memory usage slightly. Speed up a couple heavily used operations slightly. --- src/data/nifitem.h | 23 ++++++++++++----------- src/model/nifmodel.cpp | 2 +- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/data/nifitem.h b/src/data/nifitem.h index 3569d0a22..7b7d9f868 100644 --- a/src/data/nifitem.h +++ b/src/data/nifitem.h @@ -325,7 +325,8 @@ class NifItem if ( at < 0 || at > childItems.count() ) { childItems.append( item ); } else { - invalidateRowCounts(); + if ( at < childItems.size() - 1 ) + invalidateRowCounts(); childItems.insert( at, item ); } @@ -471,12 +472,12 @@ class NifItem childItems.clear(); } - const QVector & getLinkAncestorRows() const + const QVector & getLinkAncestorRows() const { return linkAncestorRows; } - const QVector & getLinkRows() const + const QVector & getLinkRows() const { return linkRows; } @@ -706,18 +707,18 @@ class NifItem QVector childItems; //! Rows which have links under them at any level - QVector linkAncestorRows; + QVector linkAncestorRows; //! Rows which are links - QVector linkRows; + QVector linkRows; + //! If item is array with fixed compounds, the conditions are stored here for reuse + QVector arrConds; - //! Item's condition status, -1 is invalid, otherwise 0/1 - int conditionStatus = -1; - //! Item's vercond status, -1 is invalid, otherwise 0/1 - int vercondStatus = -1; //! Item's row index, -1 is invalid, otherwise 0+ mutable int rowIdx = -1; - //! If item is array with fixed compounds, the conditions are stored here for reuse - QVector arrConds; + //! Item's condition status, -1 is invalid, otherwise 0/1 + char conditionStatus = -1; + //! Item's vercond status, -1 is invalid, otherwise 0/1 + char vercondStatus = -1; }; #endif diff --git a/src/model/nifmodel.cpp b/src/model/nifmodel.cpp index caf04c4de..8104a1b86 100644 --- a/src/model/nifmodel.cpp +++ b/src/model/nifmodel.cpp @@ -436,7 +436,7 @@ NifItem * NifModel::getItem( NifItem * item, const QString & name ) const return nullptr; if ( item->isArray() || item->parent()->isArray() ) { - int slash = name.indexOf( "\\" ); + int slash = name.indexOf( QLatin1String("\\") ); if ( slash > 0 ) { QString left = name.left( slash ); QString right = name.right( name.length() - slash - 1 ); From df965f6314dae1dac8fefee8e94627d6bccb3fbb Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sat, 16 Dec 2017 18:57:16 -0500 Subject: [PATCH 145/152] [GL] Try loading DDS that are smaller than calc size There are some old DDS files that I have been informed of that are not actually the correct size for their format. Intel plugin resaves them at the size that NifSkope calculates it should be. The old DDS loader loaded them fine however. --- src/gl/gltexloaders.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gl/gltexloaders.cpp b/src/gl/gltexloaders.cpp index e0d152529..fd4ef13fc 100644 --- a/src/gl/gltexloaders.cpp +++ b/src/gl/gltexloaders.cpp @@ -1104,7 +1104,7 @@ gli::texture load_if_valid( const char * data, unsigned int size ) std::max( Header10.ArraySize, 1 ), FaceCount, MipMapCount ); std::size_t const SourceSize = Offset + Texture.size(); - if ( SourceSize != size ) + if ( SourceSize > size ) return texture(); std::memcpy( Texture.data(), data + Offset, Texture.size() ); From e2823c88c8a57ce8dcfb05bd357ca1e5ad4f7e7c Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sat, 16 Dec 2017 19:14:27 -0500 Subject: [PATCH 146/152] [GL] Provide NPOT check like old loader --- src/gl/gltexloaders.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/gl/gltexloaders.cpp b/src/gl/gltexloaders.cpp index fd4ef13fc..2afbe8cf6 100644 --- a/src/gl/gltexloaders.cpp +++ b/src/gl/gltexloaders.cpp @@ -1172,6 +1172,15 @@ bool texLoad( const QString & filepath, QString & format, GLenum & target, GLuin throw QString( "unknown texture format" ); } + // Power of Two check + if ( (width & (width - 1)) || (height & (height - 1)) ) { + QString file = filepath; + file.replace( '/', "\\" ); + Message::append( "One or more texture dimensions are not a power of two.", + QString( "'%1' is %2 x %3." ).arg( file ).arg( width ).arg( height ) + ); + } + return isSupported; } From 6798b9a1bfe72f993625a740efc839b40c7f3d6a Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sat, 16 Dec 2017 19:46:35 -0500 Subject: [PATCH 147/152] [GL] Reset shader when shading actions are toggled Fix issue caused by refactor in 31fc1b2 where you could not turn shaders back on after turning them off. --- src/gl/bsshape.cpp | 2 +- src/gl/glmesh.cpp | 15 ++++++++++++++- src/gl/glmesh.h | 2 ++ src/nifskope_ui.cpp | 1 + 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/gl/bsshape.cpp b/src/gl/bsshape.cpp index 28695694e..07c800a98 100644 --- a/src/gl/bsshape.cpp +++ b/src/gl/bsshape.cpp @@ -34,7 +34,7 @@ void BSShape::clear() void BSShape::update( const NifModel * nif, const QModelIndex & index ) { - Node::update( nif, index ); + Shape::update( nif, index ); if ( !iBlock.isValid() || !index.isValid() ) return; diff --git a/src/gl/glmesh.cpp b/src/gl/glmesh.cpp index 856f09e48..9e1c51b81 100644 --- a/src/gl/glmesh.cpp +++ b/src/gl/glmesh.cpp @@ -107,6 +107,19 @@ void Shape::setController( const NifModel * nif, const QModelIndex & iController } } + +void Shape::update( const NifModel * nif, const QModelIndex & index ) +{ + Node::update( nif, index ); + + // If shaders are reenabled, reset + if ( !(scene->options & Scene::DisableShaders) && shader.isNull() + && nif->checkVersion( 0x14020007, 0x14020007 ) ) + { + updateShaderProperties( nif ); + } +} + void Shape::updateShaderProperties( const NifModel * nif ) { auto prop = nif->getLink( iBlock, "Shader Property" ); @@ -182,7 +195,7 @@ void Shape::boneSphere( const NifModel * nif, const QModelIndex & index ) const void Mesh::update( const NifModel * nif, const QModelIndex & index ) { - Node::update( nif, index ); + Shape::update( nif, index ); // Was Skinning toggled? // If so, switch between partition triangles and data triangles diff --git a/src/gl/glmesh.h b/src/gl/glmesh.h index 01c3b4d56..23e989ede 100644 --- a/src/gl/glmesh.h +++ b/src/gl/glmesh.h @@ -60,6 +60,8 @@ class Shape : public Node Shape( Scene * s, const QModelIndex & b ); ~Shape() { clear(); } + void update( const NifModel * nif, const QModelIndex & ) override; + virtual void drawVerts() const {}; virtual QModelIndex vertexAt( int ) const { return QModelIndex(); }; diff --git a/src/nifskope_ui.cpp b/src/nifskope_ui.cpp index 9f0b1ec05..b4fb41e17 100644 --- a/src/nifskope_ui.cpp +++ b/src/nifskope_ui.cpp @@ -250,6 +250,7 @@ void NifSkope::initActions() shadingActions = agroup( { ui->aTextures, ui->aVertexColors, ui->aSpecular, ui->aGlow, ui->aCubeMapping, ui->aLighting, ui->aDisableShading }, false ); connect( shadingActions, &QActionGroup::triggered, ogl->getScene(), &Scene::updateSceneOptionsGroup ); + connect( shadingActions, &QActionGroup::triggered, ogl, &GLView::updateScene ); auto testActions = agroup( { ui->aTest1Dbg, ui->aTest2Dbg, ui->aTest3Dbg }, true ); connect( testActions, &QActionGroup::triggered, ogl->getScene(), &Scene::updateSceneOptionsGroup ); From 807290b8f22a89e1ca9b0a2dddf3445a61b6df65 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sat, 16 Dec 2017 20:25:29 -0500 Subject: [PATCH 148/152] VERSION increase --- README.md | 3 ++- build/VERSION | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a78915c8f..900d5c761 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# NifSkope 2.0.dev6 +# NifSkope 2.0.dev7 NifSkope is a tool for opening and editing the NetImmerse file format (NIF). NIF is used by video games such as Morrowind, Oblivion, Skyrim, Fallout 3, Fallout: New Vegas, Civilization IV, and more. @@ -42,3 +42,4 @@ Refer to these other documents in your installation folder or at the links provi ## [CONTRIBUTORS](https://github.com/niftools/nifskope/blob/develop/CONTRIBUTORS.md) ## [LICENSE](https://github.com/niftools/nifskope/blob/develop/LICENSE.md) + diff --git a/build/VERSION b/build/VERSION index 277340063..589a5b993 100644 --- a/build/VERSION +++ b/build/VERSION @@ -1 +1 @@ -2.0.dev6 +2.0.dev7 From 82358f92ba61f923f4b37930a53e2b7871c0de88 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sun, 17 Dec 2017 11:44:43 -0500 Subject: [PATCH 149/152] [UI] Fix crash from missing default in c157d97 Didn't crash if your registry already had the value set. --- src/data/nifvalue.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/nifvalue.cpp b/src/data/nifvalue.cpp index c448bc99a..d1d8feb14 100644 --- a/src/data/nifvalue.cpp +++ b/src/data/nifvalue.cpp @@ -240,7 +240,7 @@ QString NifValue::enumOptionName( const QString & eid, quint32 val ) if ( OPT_PER_LINE == -1 ) { QSettings settings; - OPT_PER_LINE = settings.value( "Settings/UI/Options Per Line" ).toInt(); + OPT_PER_LINE = settings.value( "Settings/UI/Options Per Line", 3 ).toInt(); } int opt = 0; From 5ec306b58c1fd9b9bba40b0d24f144f0ecd28001 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sun, 17 Dec 2017 12:32:39 -0500 Subject: [PATCH 150/152] [UI] Licensing, About cleanup, version string change Since pre-alpha seems to scare people, change the window title text. Added GLI to the license section. Moved the qhull copying text to the window, removed the file copying. Added Discord links. --- NifSkope.pro | 6 ----- src/ui/about_dialog.cpp | 53 ++++++++++++++++++++++++++++++++++++----- src/ui/nifskope.ui | 30 ++++++++++++++++++++++- src/version.cpp | 5 +++- 4 files changed, 80 insertions(+), 14 deletions(-) diff --git a/NifSkope.pro b/NifSkope.pro index 4109884b8..c4f4582e5 100644 --- a/NifSkope.pro +++ b/NifSkope.pro @@ -482,9 +482,6 @@ build_pass|!debug_and_release { QSS += \ res/style.qss - QHULLTXT += \ - lib/qhull/COPYING.txt - #LANG += \ # res/lang @@ -507,9 +504,6 @@ build_pass|!debug_and_release { # Copy Readmes and rename to TXT copyFiles( $$READMES,,,, md:txt ) - # Copy Qhull COPYING.TXT and rename - copyFiles( $$QHULLTXT,,, Qhull_COPYING.txt ) - win32:!static { # Copy DLLs to build dir copyFiles( $$QtBins(),, true ) diff --git a/src/ui/about_dialog.cpp b/src/ui/about_dialog.cpp index 716714608..e7ea81e9c 100644 --- a/src/ui/about_dialog.cpp +++ b/src/ui/about_dialog.cpp @@ -18,11 +18,11 @@ AboutDialog::AboutDialog( QWidget * parent )
-

For more information visit our forum.
- To receive support for NifSkope please use the +

For more information visit the NifTools Discord or NifTools forum.
+ To receive support for NifSkope please use the Discord or the NifSkope Help subforum.

-

The most recent stable version of NifSkope can be downloaded from the +

The most recent version of NifSkope can be downloaded from the official GitHub release page.

A detailed changelog and the latest developmental builds of NifSkope @@ -37,10 +37,51 @@ AboutDialog::AboutDialog( QWidget * parent ) Copyright (c) 1999-2008 Havok.com Inc. (and its Licensors).
All Rights Reserved.

+

NifSkope uses OpenGL Image (GLI):
+ MIT License
+ Copyright (c) 2010 - 2016 G-Truc Creation

+

For the generation of convex hulls, NifSkope uses Qhull:
- Copyright (c) 1993-2015 C.B. Barber and The Geometry Center.
- Qhull is free software and may be obtained via http from www.qhull.org or GitHub. - See Qhull_COPYING.txt for details.

+ Copyright (c) 1993-2015 C.B. Barber and The Geometry Center.
+ Qhull, Copyright (c) 1993-2015

+ + C.B. Barber
+ Arlington, MA

+ + and

+ + The National Science and Technology Research Center for
+ Computation and Visualization of Geometric Structures
+ (The Geometry Center)
+ University of Minnesota

+ + email: qhull@qhull.org

+ + This software includes Qhull from C.B. Barber and The Geometry Center.
+ Qhull is copyrighted as noted above. Qhull is free software and may
+ be obtained via http from www.qhull.org. It may be freely copied, modified,
+ and redistributed under the following conditions:

+ + 1. All copyright notices must remain intact in all files.

+ + 2. A copy of this text file must be distributed along with any copies
+ of Qhull that you redistribute; this includes copies that you have
+ modified, or copies of programs or other software products that
+ include Qhull.

+ + 3. If you modify Qhull, you must include a notice giving the
+ name of the person performing the modification, the date of
+ modification, and the reason for such modification.

+ + 4. When distributing modified versions of Qhull, or other software
+ products that include Qhull, you must provide notice that the original
+ source code may be obtained as noted above.

+ + 5. There is no warranty or other guarantee of fitness for Qhull, it is
+ provided solely "as is". Bug reports or fixes may be sent to
+ qhull_bug@qhull.org; the authors may or may not act on them as
+ they desire.
+

)rhtml" ); diff --git a/src/ui/nifskope.ui b/src/ui/nifskope.ui index d7f75f37c..d4b179b5c 100644 --- a/src/ui/nifskope.ui +++ b/src/ui/nifskope.ui @@ -327,6 +327,7 @@ + @@ -1373,7 +1374,7 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 - NifTools &Downloads + NifTools &Projects http://www.niftools.org/projects @@ -2067,6 +2068,17 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 2.0 Light + + + NifTools &Discord + + + NifSkope Support Forum + + + https://discord.gg/ZFjdN4x + + @@ -2612,6 +2624,22 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 + + aDiscord + triggered() + MainWindow + openURL() + + + -1 + -1 + + + 639 + 399 + + + openURL() diff --git a/src/version.cpp b/src/version.cpp index 484695b4e..80c2bfed2 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -192,13 +192,16 @@ QString NifSkopeVersion::rawToDisplay( const QString & ver, bool showStage /* = QString verString = QString( "%1.%2.%3" ).arg( verList[0] ).arg( verList[1] ).arg( verList[2] ); QString stage, dev; - QList stages { "Pre-Alpha", "Alpha", "Beta", "RC", "" }; + QList stages { "Dev", "Alpha", "Beta", "RC", "" }; QList devs { "Dev", "Post" }; if ( showStage ) { stage = stages.value( verList.value( 3, 4 ), "" ); int stageVer = verList.value( 4, -1 ); + if ( stage == "Dev" && verList[2] == 0 ) + verString = QString( "%1.%2" ).arg( verList[0] ).arg( verList[1] ); + if ( !stage.isEmpty() && stageVer > 0 ) { // Append Stage verString = QString( "%1 %2 %3" ).arg( verString ).arg( stage ).arg( stageVer ); From 2fbd0b161e9efed5f2959202ea31ea950d5859da Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sun, 17 Dec 2017 21:09:58 -0500 Subject: [PATCH 151/152] [Build] Update docsys module, NifMopp dll NifMopp was recompiled with hk660 and also fixed to actually work for subshapes. Only copy it now when x86. Re-enabled copying of XML and QSS. Commented out the TRANSLATIONS as they are so outdated. --- NifSkope.pro | 19 +++++++++---------- build/docsys | 2 +- dep/NifMopp.dll | Bin 843776 -> 1686528 bytes 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/NifSkope.pro b/NifSkope.pro index c4f4582e5..1c0bc5a69 100644 --- a/NifSkope.pro +++ b/NifSkope.pro @@ -34,9 +34,9 @@ CONFIG(debug, debug|release) { # uncomment this if you want the text stats gl option # DEFINES += USE_GL_QPAINTER -TRANSLATIONS += \ - res/lang/NifSkope_de.ts \ - res/lang/NifSkope_fr.ts +#TRANSLATIONS += \ +# res/lang/NifSkope_de.ts \ +# res/lang/NifSkope_fr.ts # Require explicit DEFINES += \ @@ -472,8 +472,11 @@ build_pass|!debug_and_release { ## QMAKE_POST_LINK ############################### - win32:DEP += \ +win32:contains(QT_ARCH, i386) { + DEP += \ dep/NifMopp.dll + copyFiles( $$DEP ) +} XML += \ build/docsys/nifxml/nif.xml \ @@ -490,16 +493,12 @@ build_pass|!debug_and_release { READMES += \ CHANGELOG.md \ - CONTRIBUTORS.md \ LICENSE.md \ - README.md \ - TROUBLESHOOTING.md - + README.md copyDirs( $$SHADERS, shaders ) #copyDirs( $$LANG, lang ) - #copyFiles( $$XML $$QSS ) - win32:copyFiles( $$DEP ) + copyFiles( $$XML $$QSS ) # Copy Readmes and rename to TXT copyFiles( $$READMES,,,, md:txt ) diff --git a/build/docsys b/build/docsys index 0ffd95a50..07dc05fe2 160000 --- a/build/docsys +++ b/build/docsys @@ -1 +1 @@ -Subproject commit 0ffd95a50c277e761ab664b68a827502d163c083 +Subproject commit 07dc05fe2c09ac8f1defd4e94c90fe60202cb191 diff --git a/dep/NifMopp.dll b/dep/NifMopp.dll index de659642359439341edbf735e688f6af04d24f51..2182f291448ceaa6d44c7cc6a96364ec5cdf6ae9 100644 GIT binary patch literal 1686528 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�my&e!jzBs)_IpfB9A;*&CFHbx1Q;)Xd}7Mg*V* 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

i%G6P>ggZF0l4y>hBNg7iUTD(0i=C_hJ(j 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

H=W@Setsu~PL;{fH+d60m> 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�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%0ASAWdW=xcP=3K* z8L6Hv-=pKb5Rd;(_)`;}?hI`R;=A0J!m#ad;@j9k8^?(#Os*tR8m`aIvxEa2o?A_e@&*C2L^#|Rv+#KFq z2{*&<_=t8OGq4Ij@3p{g_YlS9Hi%b{kiX6F+b-9j|{ zx$c2s0S#sOK7k37JIeiOqUKEWUgEvayV5&KI}ZeYpZ1F6svRW<7CiM|{`1*Q_fbje z@GrylQG-)G$~zbk92T4xTo_yvtl%E#f1nE40HQTRgrKn`(c zPxCnQVsjcN`Z4oG^GEX$>q+Y+yvToB=WwF0a*EKEUv*4S;06AroaSx*Z~UFf_KYL} zGtIryofx<}AXKvPxE?Q%An8ZmMGuvbG182lJ|8|j)vu4Ay3tZ%5F3tOFdPc05`e~%=w}EgW3!AWiqPIa+HC~w88pG zdJw$#IEb%7|F?dW5yUmiMhzFN;t6oy2Pn&9eHZwq`=0Ur;PVu8l=XR_RtPFz%Z;%W ze35KiXdUyyN+NEAhcb!OLcVTymyo?N1nKtk${izXBJ_8|t3G1A*0OH1Dz6U4se1Kxb);^F8dn+D z;Ud3a2u0Jv7j9sE;-pg~?)fs_;!n7uD%#d!`vLo7u+DI}+H~hl^aF9zPa>1^yx$32 z$m+ckcr&mVmFHI!&r8rccj6!M%WFD%s`*bTXlEtrZMm`@kGn#tM8~aCs>vMGDs}jD z^|&dG?A<0om=rSp8s^H4?;o2m)V<1I$#IRkpdt+<+I`Vv0=RBrDH))LLcjw)R;)Q5z@t zFXgAshbuY(Pg6&!k4*<NPIu?|pmfLa1v<%^AFU1q*$vk+q?0&8KZ9gYA^dkGKF$%+MXofq zz`tXBW9`ZI733UUn9()hwrVg+w0oQz6U+$S5_~jR6+9X>a;JEA$>EaGH_{pyY&=e91ekxHG)`I$nd zq#xYkHh%Ue;%6(JWL_;r@hYcKy>gXrlpnx4QDE0qq@|)syiA3?Z`OMmC-C_f8*53q zykgW#M$^Z4jPF$HQlLkwtt%vIU2`Im0Ws!Fwn^ziHD+{w)W42v=S+9GScM z6?T0JS)i#h9dV@9$r6r4w9T_+b&~b+K#DEFO0<%!WYAQqHH`DEvs1>P06!aiJ=g^2 zIxTcD$)I~eYpLns_ud1)<1S@2X}*)GnQPSfV1q*H^GoCa#M65UI?38E@P3jy2#x=J z^r2xmHKS1e{!Nykj9Y)3Hi;Bfss03t<0*#EoX9=Bie%)8*16VWob8^dn2(d?sIqVLF4b|M3s7Q6_>=*gg`GW>IL@qdmZ4wRewqzWx~N@SX0d?;$pE5u|Zk<+PBKT>ZE+>3^BeDI{88B8M& zv=v|PGg5}0_|6g&bx}Sb^E<`+H5$S%_+8hNX}SmI{WLex>*$Rqauew!kM8DVtuyO! zeD|9r)YRv!f$Y;M&L%iT9loy`3V?#n4NW3{dL_BKTJcVkJ4?-?J9mi#3K~ln#9ip& z�F7G?cQJy^m^>(c9k8k46Gk3y%c6%?FQ3^^} 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 literal 843776 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 z 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

5&Ldrjf7sM33h3i`K8C^hA|ryHzl{ zG2wrHr#bCJIzc-Bo9SlSsRZea4NBDvdYGy(C{;6z zC}vt{*(E+I!X$-irW32g(8-*ELe-FkC{#g*e;9=-TKi!Xs%ZD6IeWNkh?>$AD%p`1 zLxr07sMrup3e}lQp>_jLK5#=0o|KrM zu#U+u63~7zs?0|I9}%Tw=v29aj2*(>QeUQ3Dr-I4_o%*fmbgJ1FqEkyuI<-vFspZI4f?pYaBly4ko%Bn$G>ohFfU$WNrKPAxkth|WOj#FK*PEXg7|%jjLu zJ{Z0%qO&50=v;Jtrn8CWG=nZfbmHJbL?@;(iU+2VX1s%l&eTK}(Fsy>0O$*?f*fc_ zzPTjPDcN#zFH|9p69lKEKgq%#ZtMs`(I7a9(geXNW6DGf2+rh67!QL13B#&jxdY5`B>fWtS zqG<{D)A(yCBGXrX^p0$QRw{|=2iGJ!Y1L{7uciU6v?n{e@KHw}(r_0ZA_~@FDDYxx zSgwrf{hF~~sq7l6V&F;QN(nESsCvsp41Y;o?`8B#-FxDd{8QkbXM#^0W?(~(Ola^b zij-&rjlYbiER=O5<@zKQgGIK-OVpW&KqQHp)u*a^UCF~Er$`S`kUY3Mf;7LR@y)R+ zl>9iphY^LYQ7OqUDF`?u11L-KOJsC(Tn0QL0vrf9Dg%b)!$@zW5^;1&APMx5lLsN3 zFa;EHd}N!(r|eUDJW?cxWVR z)}X!gh5)Y!=)zl}cw5)VFUN;i8ZwNxC`ew6Sl(g>$GOFk`h?ILP;@yn6Q$~2R1iP4 znT?OUSHO-HJqOVf+Ok8HZFzN8=iG7Vm2{5K+jE_IQ~w;E!cuamdmAWU{MF6Aad?^= zxs@cLU~2a%fTenVIKIqDO!Pj;mz}4D9#B2#PFTS+v>|qlK9R2#fzADQkL7E>`ANP; zB7akV$(b#0cB(_$^U(!xaa{b(#GPHJCS^*Wp`KxnPtI1ikt!@0=i1M}IA1{`9)tP4 zjb>LY3*uv&6udYU%j-rN=68mImvTH>6O#G;7YW+M_Cezw7Pyq#|oaM^a0ZxfK&c8qTmu-W#46xQ>$M-72x z+YbzBW!qzhAinK!Zb^Q*KDnH1$%yvh6A5EnQfQLB|7 zrWhW6frpRr@KGKf=i$eB_%R;-0S`Y)P!=BM;U{=_KM%jo!>4%o1P>=gu^(Rd>Gfs> zIfd7D(9)`f_z{By(c>u$OlJN3|1c=0e|7=5d>I@Q2$i<)0e?S#IA5D}ME>ZTer-qd zwLd*0-}4Y>sB;RivxV#F={AAJ;D#drpaF>yz*a&{(k< zfS96%jb@9tLU0$TTgBNO5mFskX(tj^E=t-#Y6F5ohXh-EHOa3g`4y305h`v*`88DL ziAxl~Cft;kUpE29!9^nZbt@us_=1;|Uqkr}?eZgindkx;a3U639YW|zHO17;z=jY# z$>t3TChWhj4Ewvl6RCe$BxX<&!*qI*dVt~^eIY$P7y5t=9beSf5`4+Nmf@{j^qL^A zLc9^~^@}&^x#7J7Ica;QG12gpb^T?M>;UrBlO(&kF?IH3gQ>Xf`+}+H&tfW8XE7D) zvY3kXIZVYT7iPeAMoIRinTr46r7Wi6|EVOKcuq4*vPn;9W=Xb7#1LyyLwZ)5%MTzY{x8p%~Dn zL-df8>%UC|ZdD)xPnkmStv?mzQ5)60I|W56DU*v?d!V%its|)vNtaxl3YyJgt^J$C z;g6viKzsdKX3;=ZShQ;T+Q|}#OY+J*S(+M(A5ktM7LB>LY%DW&hmWlCBnW$2zhFGh zEE4~L?9=yE(I>(G10z-pd!|n!cm_GoDP%SXSo9~0)Wnw{nlF;-q9H&grjS3~A5oB@ z!X=6^RJcSTh6)!^tQRxL$==mW405v1^cOJ5e=ebi*wF%j5<70XK%J9tM40Q}pH(jA z{t`@br(}{J!;-TAC74X|U0FFc^Hr6_hR(1$NMlnq}`L`_XcFxBQk6pkL33RdV)_c8Ae4Uzqf-v z47VjxK^8?kEK?W>e?VV<0mpnR(o1IfZow=U-0RMf*Jgne-8uIdy$KdI3wMB3j_#eM z4B;+ROmEed`^NhU z-~}0P#5Lb?kQxC#WG{H<@4g8pWRi^-3>Vy4*TLall+4#Q9?RG6{dvB2^RwW+;Q0)G z6i)FJM?4q0TNe@zn481-RB9mDnA(6MSrQj#;g&JDt~uP8`q%r($)J}Vykpu0Yc!7h zvePAcMch@STZ)Bn_^w+K`$P%Fe|r`yw8Zw(EBS@E zcoBc?EbsG{(1lq@R$s~YAZ(t+f!PG=KXF(OdN}^LITZHwyEXl8SG(Tg_*EZn{a1S( zMaL-nI&ZNaEYX8r6jaO)Q&Ghy=DD&8a-BSkRIZD%v4gfZ^-3qJ^t9<;sfKL=y|S29 zmaNu;WqQ!B2kZ1;y(CjO<#`0bfwqA?UFQI|64RQ1Q_XO{HHM zwO9_0TCB(n|A=;rFgk%NsWl$fP@>lq>kSpGrruXmw|+j@G#bnj;Qg+1fQp)8AKcT2 z>-2Da^fK1qXJJPWxAkk3K?JlM-y`bL$xHe&&qoJjdh%bUp2{U&X2N*(wD_BXZ~(lS=%^i?@}He+T+ z{b*gFf(OdQBI1F^dEhY~*uw)4^1yB$*u?`s;DJYZ;1M2pmaZZH2&`VPo-k5%Gjwvjhy z1xJ39Z(HvQq`vj~)D(X+e#lvRJU*qMdUeNfD>{uMNuv3}$QyZ(_p$RMznz_^s5AkgCHF>wfJsph*x;$0%|E>YAjQSvU4V3$b0OJv_AN{jW1TIzVLPrTHl zr{X1mo{EP&07FH13VDpyZUfrm+$JO zzXts6;DI4^@UO|?*lIgK8CcuxdujbTU>H|?xDL}-bRH9zFNW*t_$UUF3W<++a0QsR z9OeEPZ#l;O1H9!0?jPbUNi>GHocQRq_dE=L#IGbrtyxQr?62Tb5!pNeXqK~Og+M4| zQ-V;TyE##Dzc8ubSpTVzypO&hc8^VbgJ&V>l0;yyLWb&9E z#QFh$ETAw;!r6$iCZZlaD`!g;K()S9q2?o?SSJF@4LzbnwN{7#p~cB8lwujWA7RTB zfC>;VvE$1CCdBJ0lS)9@)^{nYjDJxKAIrm3m7%TYmM*k{0Dv)&%EAC#i}x!{9YMA>v;QAyfb!w5i}h z0D=c$4LnFN(23_BJX1eDhvoRV$cnDOGWcZg_1Px7(cg7ju#%^~U^};ViS1mNF5G^% z5+F0y-j-`D4P7zslQt>TekZ#f=04npQ*Tf#_c%+>k-E@`mQPJeRQ%LVO^SM}@hiix zFj1zWPk!53*#@AodSL|Sc~)0kROg8gd*Y`x-&=io>@Cx})AMBiyg?(|=k7VaNb!c* zdW3A#luyjhSU;!1!VH!t93?DiLk%DfXgDkIUWcbE@gLC=BGHR&W4;k}YZC?@kWt}# zPL2NwF(lmebNMro8^_rg-y=5aS zGh~Pc9q~*F=iw+6s*@nl;|J)dAZk3|tE6UEdXTz!!p^KPNaU2Jwrt~rQ)Ju(>B=^J zj4En$kZ}`yS0t+QNRPXaT7LR7m?NTijL1UU=;<2~BpcOe)Qyo^DMqSnj<36T!vekA z!wyi5U z(E?pOfyxX*beX&FdIfDBP*Pv7F+2sD9~Fgo&Ae| zHc0N`ycZ?;DxH0A;bcP)uadP4~ou19WM^jQW^;1r^V%dw~7{Dfkx(Iqe;+Wk6z0XyH4R1eMsCdUn*d;C~S^{xpC7z7!I&4l#Nb-v@?$D4NW?*cdfBYtkil) zm%$apE>TarM13QM;CHuRm(Ufm2h$fZ$>I(Hbkwp0Fa3KkZlNrzdFf9n%Q0m?VLz=obXdMbz8F*VbvVv%gXhbwd zoJ2H6Tp${G#0@V&JmTS%ULNtnU^|Z#!W$5e6vJ>YkCeb#FOQVLD-Dm7!y65cz__=c zM?jQT^9YF2IvxR0TF)aON&`FsqO^%eK$J#!1Vm{okF@g24j$>?m1}rp4X^CtkuF}j zjz`w<%5EO%=9N(%iSo)`9_i(keLT{4K(XWyJV7ykWa`qK!?SSv=4)`Q_l#Min8LA2 z#~M7;n*a}lG&Jz?KtAXV3hi@jy+Y=%m>N#yE%;!HRcTBs{T&>lBcZzUKFleC)Te&N z`i2~s&IY?w{e=lXSZK4tzF@80TBE^cp|t^a4xKRjxRPB)5CyxlL}Ph#0Bh09Ai>70 zk!;9f&B*Rd)M`N+&6=Cp8-uBHcc5Zy^MHx?(^lUb>W&^-D%8^DTFO;|ShV=z5B`MR znQAk4=W%wY;=9wa{_?ScM`8U%bec#4k6xq1M;-c|3TB^7jXGJg9d1}cp~lo_pRicS zoMQ`YVXfW|UXTT^=`m&_V-^izV}1~1^so~%AS<5uq?InKUg;K7gUaZ8D(Abm&=Q@; z{Dm+bXt&obgvm2>&OSTn!CSf}`Em~Vtzq`oSb^f4t3|KOrX*q1RavV9$pGiyd&r3 zOv6^%R|7+sp%iR6PC@+z<5)U{L+bENAbR7zc~Hnk3V3Tcg#d)Vjt3~r2!5^jb>O!K zzb^c`iDbrbse=y^k4>N^wowTp%54Bf){O1G$Cxp~_t=+>8G{W(7pp2^&8;8_u`htq z8bpIFtZV_1549aV`D}A!BL6Akr^q`Iwhw$SCH_Hls}CeTZ83oLyBx3tD7PEWgv0l% zyU4VHhl&EAlFP$l)}YA_W7JqgQJ;kGLJ(k^?C~+OA`1(PBc}=y19p!ad1~p_zLy2G z1Y{iOq~1*TDQJ|?>s<)Ic|yZ9&_67kU;@rK5Zc%rPJQdYSuM!*7(RiiZp!HzC)p7R9u!w)p~qmD5eD2yD-uv z(J$sxI&AE8`tsbh9Ar+hBF<@6OvDZ7Z!ghEB!?(wb*;=X2XAg)po6G_p1I5)v61Mc zd}6_VnseiWcoQDqyXxRAL`baAz=9tf?pcGHU6?__7aj*N955F=&H6?*;|2qvF>GH} zOf!^8tqp`9*H|&*vhBcTSF$Am<0~Qi1#wEGndZ$wMP7COPg4_js!!eKWKm#9m{Zjk zQ<1CH+)z#pS3C9%sLdRx+SWx$K}WFmhh8jSU`8>~PSi^#2v;k#csoqW3E7 zZoA%VXLmdFUU0pz8Eaq;_WTN_Y;!hk6X9PzW<6?RdKx>I!1VCo3+vb#LG%j8T0n>2 z9>Q_HdLLmu!7jQ~Vf$w;3`RF$UV6Dx{ox18E`n8GnX1H0pKfpsWsah6UIsiH>ZlfkEOOFz0_oKOzX;NORd%k zg0~ig&`b;MGcl4bGrUtL+>g7Df|NcH-SVnbbx9hC*TLEDrBJ0hxvXxqivJ{l{c`D1n0+G1C)v7Pidn1&& z=LoDi3uM*rV)KrI9%2jD7GvjF3QBma#)&E|@qW$PUnE-zd-CBBYYPOYegVF+Qw&vO z2*sfrMz6(Uubi5@tB z*V5l5)_5(pLgrVnFT87iNV9E9>$=WrcWP&KrDz;tlzF08~PTo5NBp>?wU2 zq%jz>(OcLY+hKN)tm7WGJw13B1OJDjv%p)-T>x)@!wP7Xp0`0^m)WjhjaVg=3-g2x z3}aEu@`C8$@C(?F#90QriQ$US*Hx%@WllS*aFh`P4S9+-y3CR{@i(zemb_@YB&LEG z@GSIy!W{4np-R&jR>W0^q-01R!0U3-Bu}`lD%x|A~Znqr6^l;4=UZHl+mkRRZ8wP+9X# zfJeeE3Gw45#D6Ud;#XY^@ueoje=RHkUN8^|=;?mCMF9O3#aQ`bj1AD2h{Y=q3riUb z@p%KUhR?kE=Tg*jZ02`@0CxZ81bGLoXnt zb8OZ;3wx0EGQHiy9;9tWZ}+kX9~3WzEVc(Pr7!!Q(`$?25x}W$aKj|Et5aWj1^ff( z?Xc?o^|Jn}5=*DP{qB2fH3nF;hEwxDj+KANuEQj~zG1Nm0FsNE1vhCRkd(Ix1fqd; z_E2*Wa0|g!DAfS;l{S5EoV;=%FkO;7byXVRWRwK*qgfPIojUa2GbLrPE6J!4tytAu}>fl>MfwBA0bqswP6`$PG+)H<9sY9 zm=4J~&9!eapr1%LM;iKZ&~yV!;fNa_ZUqnLZ|U$Ux{JYV%tdnP9up_TojGTcpr;OJ zOryuesp9}0P+VDOOKRbVp5GlC#rfT7wlcG<#rZu-w$dA}V_~>e==TCcxDu6CNC#|7 zOIV|1#3tO^Vbr@h6i93lM~W>YL_7ske{%%9ZfKIMun>C&r{EXhdq5!e_#v;!Nf-wi z>bsjx)3dTp)6l2ao0a&OL%$osDxwzLYb|RnVKAV6{{uOxS$#D7XKdS;fM29B7FYpB z4+}5RLs0bKDgceT*h-Bp3Iw+tp!PSW{@9pjF`Db3BC~d#X-zeck#L`4HnkaS>FSG{ zDgZ{xUu4l|pbaw)5#l4>C~ zQ={JC?7?1yy@KrWgI&VLeLwGCoLCB)e^T$g01B`d;HS|>@;d@8*5GOcl)51k&;raB zY|N*j2&6u73mA+B*Ux1-w4G_{WHZxKhw-2Tfte8FNWc=!bMC)A-a*i0Mqk04CI>;4p^) zwH!zRtS5m7OHxn#Q|^+HH~PYN!0To4cXWAeTT7$>YfuVB#!!KITE2Jp4*-Y@7+;d5A=_R()Qj({V z6dAmP)aCybJLELBUW?*uAw*Y4fR$^?4UrdhBvQCrI~gM{u1wpO{{O$nAup@nG=$4Xg^LelN&T2NEmbhc7mTqpPNlLkHA{2JQGdC@n!$8>gNPQTFw2 z5N&I135O7V1y|vm`nR6*Q>DXtZMR+im@NX*i4Oa_U{0 zUWoHBvA^?hmY!D*6%YvNZv^!_F&j~N5U$s0@fwA6#)%79u0yBlPDSs53J8FRxULR; z8T6Bs(sK|9qA}{uEvPNA%*|#eoNuu?ti_>6E~xujaB=}Uxmb~1tII|zN&ZTyd4&AWZ z7xij=Td6-|l?s|+iOCU=z!Z^i<4MR~IQg=X*9f4?Gzf zwm?LT8L?}ouEdy)MpeCJ)bGX)*L^i4ndBw}v^vq=$dw1hsd}#;byxW2MA!SC?I{2l zdGrE(PJ9!%A|aY-LKqh4CFqq&f%mqYA?6mOP0+eBabVU?`vY^)cu@yffTlpMs*WwM zXY1X70&hYY0#rr}W*S0Dz}Y1|4*F+`5P*D~UvKe-xq7b$+c`$On-?A>RFXwW#sSX~77VF~F(9Cw3NS5{aj}Z;0lFJw|i9 z#mJs;-v;Mawv)cM)%XE~=g_AxM0Evur}GPQZAFuyAUoiwu6hk?dQ$0{!hNcA>t03c zU`?xU4U%`14wu(lsXj3LR~R(xKQ^>zP1Fq@H^D6W*s42T(yL!0k7~FnM1isXMYi=< zBgJl+qA4c@1x`?Y>>QN8J|~s}<8U?hwtDY!;vJy-V8;7_YZQpRiUHV#>b<`Oa}^^Q zL)z43gBWSS3S^(AQ&w>5MYMV*#RhTGqv$%J)IlQk$TVdQb4xVSK=H#qW_=Lr@8zZL zv`U9TtK=q{t{nn78V;>shhR?2$lYFL%fK*(ai}uFf)xY9tdeMUm%1B-9Hbw3r|&}8 zi$PaAA;^-d8ldfo`maMNq>b2wpI3iE02%eflem8u@Gh}k=Q0k)D=0?ygi~sKIJ+or z>KO8qPKhiKxgcvxI36I_hWaA$ZKX!uY=AeaD+X|9YvK`bdXd&boJ~t~c0#V&2^BAd zArM_AqI(q!YN}8Np2$bZ<3Q-p$3muvrGF8oB!amvE**aRpV<@M0c60`HlZmM*ouBc zi_r?uOgNAE4qzqsqM&YMFTt~1jb8~_?JiLJO6QjI5OYBBlq&2g0CnFtA+H8{11O})mh_d60H zB~e47^fGq)1tGY^7JfIjaAXvMAvca62*DQKi@ofgyiRrnT&v=1ai`qDo!~ib89Rjv zh3M06zGL^k+i67yY5zt?p)7b``~cKR-MBVaMtT9R9)@;1x3>ZkM^qeEOQ!%811|&4 zXreGC@XjfS9^i~Jb$}z#5CWJuIpX{IOxt+569Y7cZMEV_AFXx6 zFXYvjyK1pL69fm)Vh=J_i>(>*Y;Z-9y(4>fUWZ+|j)ZtF?8`WDVg|80W1%6a7o*XQ zOi=?;OOFq{jIG42K5zhz7ZD%Z}`j3T5vMC_Chc_X13t~NpQQ6Rv&-vsNhL){q% zuo%D%O}Kwo;4U$N8*_)YxoBkK2Mhpz?W{PPnqzOkIAL1><0sMVTzcS=v}$Oo*kXA??d z$KVc;xHrKPXv87_S3=>ow;aFEveiy#(h%7oxum7TzO#CjL$9(&D~t(vDRYL!(V!Z` z4cF7>^!1bo>$0_f3Gv^U^BLVa1VvHbvwd#^7a+pSk7gFnpy_CAh%X`9{UOo5NFlWP z(`u9<2o@z7Id~4lvss9FQNg&XKtkd38b}kdCh3g2Lr}Rh)bqF?qEXoO>B2xAJ&X~V zRwl{hzlSo@-ICsxW-OA(!s$iQwlhPKAu3YpvSt|6P>D&)XG$>NxDjkfq)g?|EPiYJ zOw)%lTPJh%)2JlD^ulr065CHb#Whk5`fZ6JA;+UF=~~<{+fdPeHES+sj{ga~Me5+R zi2V+DWG32qLq*?f=7jEeJQw>VFdlEM$?(Hi|R(HYSj#dht2-kw1Gt$KII zn12moVtRL%-n|aW3wn1{@9xF1vkzN(Z5>+)m(Eb)uAv3z?PRq9EIxPXVR)eqL*%Q6 z!E|)$xb@lK(HpwJVT;vzt=ns_eblkNnKbX6J)TEx(ZIO_vwD_;qGWmH!Uc5n^B`rM z)TW6tQ~{NG-w`NR9ib!(ecK;FMBiNR+vjS%wxQZRYy)6M zH>du2ghrlpz4UN3gm-#P9r|A^FO297^+*V`JT=sC6~qL8?lC((5nDRJeu-I_|ckr0y@?vE+W2N4)YG* z;KO4Z6>g$E?BtILWT5JjsI?dUN}k8R@{Zm$$-XkFcU@q`HZb3A(7TkM(sC^N1!)Wp zYlFu(b2J#_Or5%KWgc#$;4VdcxKLjSQha{=JyqTI4w6rK6XhQ-fb3H9J+1D14I!P@ zujOH?h-&^id^)qISKo0E{LRHbicuup@p=L;@!?n}w7-Ex7DF3hv5^kNfWNN}`~y-j ztz8dmGC#DEo=yGjjgrYtrFqTLzOh)AbR9~9QgVBs1dC;!6zYAT4(qYjlD9kln zfj)iX40`P>8d3K>XAMKCqJ~r~T%BXJWtP0m5TsK-DN~#2j1;ih@s_7R)BdVJJBi;* z_?2h9@5cLo$B&)|@oU2`fZxC3M`;&f8cZH842go8{Zy!#-QCqWW>Mf^`-^$tbmp-y zDqx4QM!Vp0)`JDQJs80`t9>Zox`S1&)2(AQ;@~z`fUc&Q!eZCW+UCIdRG)e_0Vm=} zv{s?b`Je29>*E~xtrGu0Nwj+a$T(Ukpp+x_M8L5rvX+vUPv>rAU>%yC&G?RH<#ip(EkP8 zMt{=5+U$uM$F%m6G~seI7$+Ft=lhbYCl(=@(Ux070k9J7a0vg@=Vdlrx1?c`M-jMw zCr$j9XF`H($sx(;!uEKU&I-0D*oPl<0h@T?K@y&09Op%P@u(aQ|jP-_>OOkTKaFr zHNONYT}Lkmq}l+fcL7qn^VnSqphrTg0RUr!pR)EN$OWEBEx=JRvqg!Z4=!W{iQ33p zLu_LNDpbzsT3lKMN%h6fF>3*8TZ-B$^KjJ~I~LUj5l@v<$91hx3GRwPQdLz^RH@-& z1`GL%g186cffWui^%81&UkWy$)wk5SzS$`1b01O^n-qm*6&3fptW30>8a%NA zH%N0seCR$lx#*_=0<))s05oYT0Rkli0##)+(T$jN7Y?8n6!%$V&4I@0;{gSE3v5#m zqyf%)ZUv-KH8jFGkY-fVOMunrBK~m@m#$pMP1W>Gd=?o?k#X+yj0T7zqt-cAc^yGT zs0rs~fFi&az_~65hH~4-ZpQ@&2kmRKS;&JcJ$jWV?z8kLqg8IJxJzNq?LCD=Y*qk0Aonx*brB3dAA&8^rlGQNr~oaXtnl2PNEP*Sn!NVuWFK0zl(AlY-ruuo9cP>e>R0 ze!ciH{9Z0B&=S`bXy3x`U-A3n^#$4k2phrgf8lon&=Wm>jGx4e@~^!A2d*wez|<=U z2*n19QQ=O<4DRlwEaT11A$ekM@lLE$ZuygH`-na zr5?a14jL1SkP2ynB27#Lq7cnEd+50M(yxGnd6mILzS^LO*Dj%wXg9xLLAl_Fwd*&o z28n*h^ebni^irkVTBU`X`{=%nverIfvkeDQHq3r}qaFNhP{aCid%Vg%#WvbO%xWmx z<})a;I1Va+)U=L0Ojcm9K$r&d$OOt&bFsM`a-^fh{wnn_48HWVD|Ds9nY1+H*pFdt z%bE_{j{?)g5XIl-4YNC8wjp}Ay04(~b4x6Db(5%ESg;Xfw^!Xa98WEY-U7IeUKvm2 z_4rXJX%RS^L#qNo(6it2;PCqiI*uTQLSd5Y+0?67AyhCiX#f!lwc%$|&wLDc@f%=c zi^|sj=l0bYQ)l{yLApKa08VUJFd~_HuT(Gs3y^3FcQZKPg@XR)Xo}Im&r<_GMh$F0 zGu*OqIH7Pie7rxKI!=we1x+E90@27IVyK}HQbP%E3+XR4lwzo{J1GVRsj?cpQ#9CR zHkfFHRRjZto$)$N2{)_zaB-=ska!Hv)Q!M8>@0k5tS?mehH*8&p@5CS3`}BONeMfk z{%{NyU~JDOs_i9gyyytmV4v!c!Xt|B{HR~G_N(VA)x4hB<-z&DYkt*+CKl^A;BSd; zP1IW1_;ZtA9qx|5YE2a1HuA4ZVxaJ0j2YD|FrZJEsKL&POJj+^#}H0JW{(w5tf&0= zfszSZ{D2?d9-I%INa(^1NugWOn2IyH4okqO`NpHT@f6O(tX6q(icdmI1zpL*0ajmL z+?-hMNG$_NCl#ZeX;Mre97#`YIYTEb+|v%RNF8|0Q0*!}*KzhOQTNu(!ERd(-i5l) zuY}opC+4x4;bs*q{Zg9{OYQcnL)U;9NVK@lEmf`R0PdAjiQEd6ie!gBK1_D}VWa;{ zvV&~LH1Vpl$qqYGFn^hhjKH}ia5g|DBJ4YFrBT9{wD$bWNwUoD;by3`2)*?&q=AE@ z!-kU+*s~WQN0K|Ktdpma2bS%LFookPX^?vBBl63OIHZ46eie$8)+D9rPB$|un~AVq zxK%+)w5M0J4$c)|EBQWZY@u0vKN7HYA-J4fKSvJf69!KCW3>_ssAXW0Jkn&aG!^@ zUJW2}vcNjFbPa^X&J**JeRIitv@}kNI6fex*xcXEPf63ZmFnOpP$$n1a3n>hv?nxm zP(=hvcEICI)#Ucu<`0ltu3mX9%4=5N$GDO!kl2!>2lh(O#cAb~H4#f3IVah-av zOYeo?*Q57(_1;3g7x%0Sp^QcN6O5taoC7SiEUe%f%iwU|d^Ueqtin1aw%)t{h;=FM z|0%f{}Knc@ipqB+W2;%V8 zoi`MjQ+jwPFdN@S&)bFk(S;KcWJ-FSS0^JC#ciO27lSugrwDch^z%rl3%@u(y<~-L zy_UE~GtNQkzT2VW26uz6=6Av}SfY9Xwut2gKsLqd-l{ni9N(x|66-E6No*{^O*Rt! zdT^*OL{BX0)<^MoM2NrOwy@|RdmE-&SGr+wjHH9^3A>Ot;f>FVkHN}C3M?94nIh1_ zfLM6sD6c%qe!(Nhc;zu3d4X5Hz#~asndFfZyt?2NkDTI_`1?BUEJK&9=#QXGG_=AA z9kk-bu)(G1mtprSr?OnvHI^@lI2`|d0to0-pH)UXLB`WBb; z&Vm{z>3e{+N5*o6sa6!G*l^6HX4($>#y3}kJRtvgvzr@5i=IoNIroE~!6tDB+4Z5# z`G-HEIWPrw5bNNepl`uJ(EwkLAe2*66W0?Z?=Yzfz^?+(&a7u>QX(yh;q5SINCNKw z$+Ww1v~qw%)-YgXtfsl=cAV@L_SSPBeDz#6doKQ(HGaTmm`8dM2ch@@PyD^S#GQGG zyKOoc1-ORdPmiP=Q{$F<51&YLVV~+`~h3)Yk7(OPbJ#E zpzd1`KYyjV?Xw8pHRXL^s2rRkXk@&4R>pi|;`GN5wZ*T{o@?#0Te}?bk-XD$5NPdH ztlf5NHv;UZ-=pYEfO49!xHXba;W+(LIw06c@^YMXG~NqwDvkg zUT{7Wn?5=5Iiso64ClnB(BEMyv&)XQ&W?`+Cfw=1*q)hCw~WwmOu)8V_e7rU=MbSg z)q5WU=&Zg2z)|}d3?c?{Eq)vDy9>WBBJAt<#qhh|`2A-*AIEPLzgO`46Ml1%mVTGx zSApN{_^rn8ZsSMgPXCzzRyv$3k>Fb%Mk@&lxr=Xk1n(f%`4&Po{C~6eHtUP@oK`(P*qbrpPRe&6r8&oh}ou-dx2zt8`- zXy&=keeTP-Z|9zS&bj9_dCZ>gO2BLOY?pxF?73e80kh{p2_P5uRhZV1X^eSmm{U?x zs!9B1nDcl7psPKUH_vM}dChr#+UA)TplzOc`LxY5uK)!c`H+8X%mLJXPLStTlF_Y9 zTXiH$7xlO!a?nntFpdiTsO$1_t#U3@U1Ns5yBnJG5f`$%1svixY z8R{-l2Dp0R>;Dj@FASSaRUT;i^q!A5m|8IN(rvUAnr~Ja={7o-3Bavkx{a<)VBOIm zZ6(w$>0X%ghesBA$U{bK!SYInh9t`){`6z!&U8(HpYz27BB|?05X5;TgnUdG(SCDD zk^|YARsKxv0bM&>E#~Xm1G;uy$t1OFuypO=$!izGr;%X&q!5tqjlcjJjwk+la5f7e z?}iQHVS}BO1ml`U&TVBzEr)b&FJuet_4rH_+D|y~zs>Qcb`LJ*IJ(Dw{&?eX=2zEx zef>oo=&2ZZ8TNDE=CI44r#*0XnKk6};bsr{NY;?+=uo-_lK}(%7Yz4lwd$eP#mi8i zS!?lWwJyephUyOu;xUWIBO1D`;aCU9_Cd+UN4BIdxgbjB0mmGa0Y)A7-EpRd>J8K& zqMpU|VEQ-zV@7S`?6W|R+N85U-O2?BgV|5Va0fprYZ3%R>4)PNfb|S)UbeIA1rmKj zE>{a?jt+t*v@I{;q6ZAeH>wlXb&!C7fpxj++k+B&hFFx)o269-2u>qr9sivsIAKub z!ese+5}U0Ph)dug5^^3Au^Vl;MjF?=OKo9c!mla4C_ayX!SdZ;*9(a!$LiE>Xukc! zU_b0M?X|8Z5My2MO7t=Nn39Lu!R#u~mkd0td1dP4=&(k38h&6Murg1?R!nL4ei^2I zo*a9+5v6_T9Q3y|2bMC5)J7?0792wVetXtt}e;tUCxmjR71E)7IYrXXx z{RY4~8;Z>b99c%k#+RHI)_BZ3T(1UhN(_X-dEv1*CwHj88=VTdFyJg#UmZX!W};sef0-7az&3-j!X+?1qs|3q>J!~y>z9QhFy>h1aNIWAYM5gJ9i#Va;u7*sIHHS%|* z{xp`Ibba}S$Xtv$HTUJ1GOc;w=qB&$2IR^6dht7)=*KVqbtms5b0v8ndww`^8{-h0 zn(tr#*kA4Ghgb|W>TpguCPebS0eni{#~vC^aGl{_H*Ct^bq@l>&DfPvY5*_hUF>h7 z#k*t}VG{Z-AueDG=&tSZDPZ0u#06|a-zCIFq`MVU<%v|g09Wjr^@!N_QLpPo%PjUb8&k~Fo%aOvoaWci``Dd=ft5Yz-@?p_o!YT8E$bxgTvCpLz71gCS-Gf3Y z4fqlf=W*4?9v9#0g!~^l3aeYOwBO_C=RK>obR&{*@FcDc;FO|JEx*PETGYn0`A`B_&?xfNrOJNp9p9bvsk7@n5!YvnrU9W$&bqgFg-+ zC&cO&02s{FzlBp}B79o`L+1M?<{5`?w!p_y9r&1k`t3}W+#2)j?$T^R>p$zWf0$w$DbeeNP8~9^WH;PM?f8Uid zz7I3rP_9wu0f$1<3wDB$?Z$&FueAkpnFRJ3GyT7s_(&dlcdS zn)ao1%XBL}$&J!N2$Hu*^CF(4=#OzR%280-Fgg4eX2Cy=VHuxjN>;mdFBvVH4x>|& z8~Lkl|1HB?_^Z}g4EOO@Z92;EM*gbXk2Ac5ze@Dql6@~Ryy+z4H}Y5A{yM{3_^Z~D z6`btjui9iVypg}^b`Qf__^Z~@6-cs=ziJaCquiC`>d&!Ob zRksHj-ojtCF2ryje=*vHcyP4&oX}V7FyTkRvr`nBMSFn&tSP?U5x|?UjQIbiKrpv; z?m)|~>~*=Ks6E1>_Ea;5MvD5Zg&6z6pxvWdxn_Wd6hfS)`O9SOEbIM;Q ztAt#!(~2b^C^k1!>|vMHba8gE7XceN#ctPN&&U+Z2!U268-74@X#N?`E|elrFFT9n z0b~k&BF}JzarnwS|M)z0^1~hk6b6WLDP}b*LQqiyWe7(%6b2b`gdd?r;DV6lr;MIw zuSld0A3aRU6TkEQ_(#a<^Xwd#20#Az&Wxm0*N7@uTL9&m2AqJK7Q}Ws90-{%2o-X} ztzN%;vzY>@&j9Zrw;1ulHvo4f;$cilCI;|&8B>lJIa-42Ldg)g4k$6g5*bbPwiE8JRNDUo&mu z9Kcw3LBGzHP6t0{HhY;=>uh!|9huB#pQ0np+0u=0XEK|;Mvo@5*;$TLliBPi7A4)n zqSy^m6k9JvvC&eLw39`#SyB{RgQCpYY{d8!(&x3r(Xhh2sSA;yfQkhk`4E<25Gfnv z(5frp%^9Z@^aCs4e9kbVkd%IIG|~)$UT6c2Hx5hU z9cmxltNiD;Z<*7+WtsLZ%d~G$>xVe}KD4=cy|LjJf_r@Y9zk zUcC@%o{3jsej^nK)#D^{G%Ut4`{*<)EM-`knhZ5vgCIj zLDZ|p4PG$oy^HevB4t4Wye93*A!w;e~;nE=c5xrk1^N zeL0Kt$9y}cCM;K+su57`USz<`{5%wc%%d}*qpNO!=JGD z|C)JF%vHv>mqY0aV!I%=7Tf(#%-}NW^pQ36?9Z^1Vxj{eqyu}%5cUwYoJYGJR@F3B zS||rcWjkiEnbM77tyG&fq4M>H{inpVNi*zABb%HmTI5)9ZL)ehYM3$0j2$&&$IaMFX6&RHd)>Uyj3L@% z#=K_CZ^nS8!)6Q0EH+z^v&3xiANgNsK5<5>Cdv|phfX8{r9HCJH8WdQa{3(bRN)lL zL1g4^0*s0PP*jE91}pN@68k!KZD9W^gGf@L8R(Y~LR8O^Q9t?j0D#p=dFgY&_1TO* zLbdSGeYcnhKy4Df1M%rAy@=(>0F@z+MlDQvkHiHeB~J(@pkO@D3F#{f5F+J69Ym)& z{7SL~P)bTt(SS2OQkSZ-@QtU0^p!;^*dj&<$3VpKfMB^VhkNgZmbLXg@U(qQT@g$S zMIq=FsjP49Y!G_^V$YK4`wYyYl9X;b;27E#u5QP)sKX zka=u0570RKU#G_$P#40(I&3F~!Hw&PB2h7Vl~jA3Ie@4pd@YY6>Tz~7K34+OVbg<) zCA`$)$vjZO;%-?f`zJb&szG`yWhbF`8C9dd2C_7((9;1-!v*=GQ`LY(oZwA(a(isX zU)$Kzsb1JA$CWZ1$V0R!&tnA5T@xbcyW|0BU*QLY-GmVQ!DaP?w~I}%o|zFTGVlW$eDG^jt|zGWf8B z!E<=CEZLey9=x>2=b4Y2Tkk<}(A(Pj4L-NtE5G06Z)K>>+)C{&gqE3G?`P;%A~U?N zG`Id3p;6Dl0|$R`Fbx^e)j>pe;7UDRf>J3A&Oxao1~;KFp#X=WC3i}_+GUs78|oz# zy`gSA;#Zh^%{v8<$IUwhkVnls1(Z7llt<042p}WoR|E{a3m{t4%DhuxIrrcp4Gjnv zQ0qjD@pqx}HhKnO6W|);F?t38_@nh^q)j3@=17ySi5}Vs)vEMfJV5C~c(V-aIMRUW z(gutZiQ@}sPRMoI;qJ|eU3KV*L%VgU$KY{zB=DrN8k~&ncuJQrvI)vy=W!s7DL zSw1Y%_wCbA%Qn#WGyNj_K%Y$gyTu-m*>^yH;f`ST9o8=c@Mbs>a@3=Oua0c=uO9UY)g@=2fIQwIhqjvm)eHdje#71DHN z=kB|QiJj5pZraU=zHi_cIkfjpXsJ4{{R{|~4&OE~X?0ur@1l6wEHBH_e@CC4x5-O; z`tO?Xa)-RE#B{>T-TK7=9UBC^4lvFxLAw!@s-+NZ;Y!|F4pK`A6AM~*4@<~yg+#2& zw1NRs*o{(>G)Fj&sP?=3hJA+`s@T0LxRO1-M(^>p{Km~muL3^pJ)ZOqsep24k9Q*Y zEGIabJ$|kn>QZ%Xdyi)dXOE9lJqbFKDdX<}i3@vfv+V9o^4oxSEH~Nb@wE4OF2Dx- z@!2%VK2N-YnJ{sm|KLX1=QZA$Y@d&+s|7IS#%>=eIrJ!YI+^Vw^$5wrgz9uXN^Zvc zg8()yUx-gA=U%oLpH`xWq14OCh-U|4mSK<=u-26f`0XPukG|~^f&M)7_@Up37IQSc z0R5W}bjB`^BMtZWRd{kqbYZx3(Gbbo@R_Kqg`g^q>KpsYF ze&3B}lsh-~ta|G==g4kf0=f@Hm$2P_H4_^?lCs57RbP%En;gZHP34oNM`hawv4H~( zYa|9B7^_&#cbULiI~96|8$vF;uu4uoe%vFaY!DFYtTrS<-4TM&WPoDm6-*2maHKD3 zp;M3xRec2cwlg33m*iwtqTm5U;L^g_(Z?64D&(REeiOTeM} z39|(|tzou=$U$i-B>SWV8Zp>xAZjk0WJ39|0dh*6Mmp|SXONCaX_JnArbm^SjOm3z zR#`4i8sq2OY| zfI3R}EJ-iKVGfl7C{_x9h0^ph9e@Yq3&RqFjT+$+i7!MD%^%4}Jc zUf5-}pwf=?!gf>&fSrzSV?xF+b2@$*v!D$NUe{(l zWPiJ)`{92=flxK-et4$@c-z7IeRsd7W$8|wgz)l_5&OldQ+Qih3@2a;wNW>HIPW&d z`Ts-*XuoVw)V2RP{KraMa0MetuAfueg9hNiTMc6L_(z*)Aw1zP%xexd9-{hek!YWc z9NKwckC2ZzkqQC|Ox)8xy-wzy-kT@e(*onfQ80BAkMQKkvOdz*43R}rh$lB1;m4w$ ziTgbdn>tSKoto8Acv>94$@ZDq^-&;C_KnWTo++GNA1C`)ae~3w9)BhHE5={gto(7% zY~Fz9h9D30KSt(a^L$)hAs;kP0%#dfnkXjD>)wz4j3wdX?CH z0UWiDc$g2OKf=*QOg6Nk1YgR5H%RGV!;(-02`UjotQSEyJ|=0NkcC=hQXPI(B&)rWkxm*=Vws`b1v8dpR9I>Dp zGGie#R%phG%vjir6`QdVGX@vm5jX%BH{i9n0271jZ+xl25jfTiFW}k(xEX6RW6R7~ zyBS+)#yZSc+>EUxmkQDT*V^jT~`P$Bmdt7;9dl9 zA3`MY{oB}+$!$a#<~9=6{oB5dU&jK38pH*UDah4LIL>nc8})w$p}9{lPi`iT#)B6- zk$^*?=jC5U9X=i>DT`gH&gT*MykW=nE$ah3WOHMr+P; zZDpt`kfy{h7zc-{dYh!vq^)}Yo3{$hL9@n5C-m$x^Bo|aCWYm$?ltK9x=yleHTJkW?hNSQqJkfDm>fTsLNgOVPd>;QZg z`tuiEvVh+r{V5`(KTUt0NqsVABJG(Sb5_bTBU3t!@)ViUM7s0-33TWEZ=3Gq-u1-I z(isyePG|387eTVZEiCZx6buyzO;|hylW9Kb9xZy|nH+*8q~WO?q$kMCay&_RhGmN? zl)Xj>OvIrtGbARfEynxfc!FL(v=^l3zd_W3o&!5c6P*!!Bg>B43gvh;=!9C6r-CSN zR?#p3u2xa7gj-dRDe+nr_*%SI)eLUcJd(p5<~$xA;^sV_99E&ERijhTf{K&S9z{$+ z;s_+J-h>6a8F>y{K~WO>%y2z4m2b&@VkA6Be&QQ()j6^) zklut2_Mf0PpZj4Jy$L*oTyL9N|jWrWTRXrWPd@y9<<+-eLY5 zhW1QYp1k2rVG(MP1Fz?WP>X;I`U_aJWT>5$2M3Ou=D~rA3<*8spNv`r^Nq_ItT=~S zoP?$?`15#hlQ#I!m)^9&XsOW0Xxux>gPRnV`nLkhpX9-5LNddHL&u*+NTTB>B_zRl zvk6HYi2AWU-ZBr4Y)^e&tXgJn?BIZ37aB>tidxu&IcuE`2sN z`?{H6+dlsG2{7A!CSeUuTEZJ7%>n!hjZ4x?_;a-9QD;2?>#T`M_;X&QA9i))_cVGG z)6Foqp5zC)b&kt@hU*6t!yvrN&NJ}^6+nC_?6Naao0pU*ds zrT?)2pol`rgipxH4+mx+4(Xh^6F$GoTvvq8=DIL`9g;b_=%Bey#LVj`UUm{p-|p_u zv(?r=f_7wUuxs^PUXHE^QH8Ohkh@()5w73EMiH7jt{0fKs5 za)4kScNGfFidxA0rDGx3=Mb_y=oz5Ab}4VAl-D8U#ihJeQeGF!>)|-E$R3#_D78ms z2nz0z>0y`6F_XffoSRP+)F- zTmnIJt4vO)W^I+334JfE6at;Et(=n+CkyMx{qIyoFba2q1i@;CInC@urd%YqyTHCn zj{D{U96Hv%OOE^I1B3wG+ati?qIi!0hfeMh;Mgw$9Q#FpW539nVZRVKr_Y_aEdA4S z7+*5%UfLIh1+&Q*=unYT8~%~0|MaQxeo2R&{Iu1k$0NGJW!(lkfc>C)tO@nYdR{Tb zBAuYW|8|n^ZmYrmzCOwAp1JxxO#dMF`|;*v^k*kMKb&|c&JTBX$R0xCIFMn|Ivcpe zQoWd@B8f8|PAEbn>l()t35~O^U5F2Jnv#N+vyF(-H1%AJ3?%9&6Q_ap#Fx`J>#jcq znM2HLYSSU+tKJ!6o=tx%#JrZ*lc;#diFqM+0~5(zo0#WOqXB=Kl+j{%O~8+nyP6b! z2gqHL^izK{fuyfCQhkE7NYYnplD=A#^ws*nQmsk)YE9BtYm&bD43d8IG?IQly79k( zq`wv<{bG{zVDzV3Uma)MKZchKh0tpL7n2CIt~sebeW7`UCzif~H{3j^IlW72(V^$TGD>If%J&Hf*zx&R~k9frT>V6B9j}se#ytZ&HUja$rXEA{g<~ zhi$GF|A7I@p#cyB%Mi$CpdEn%23DF=*=#T|7kLC6_E36}!DtPi7ZY6oYDaWy784WV zUD?Um0h;zaW>cR~Vf%~+HpI4(%4y+XP86?lzcL$f`5J^%>b}Fk{Xfm&e!y$yZ!YOg z3>k?(xI3SPLDn22Fx`KR-ZakO@4dj{u)To4H&@G(inKiGQk>_Y8yYPzqd_woGNCgU zEy_rkAO?jZX;WYlc+ZeG5qRqv(rM)X{a+|;+Hw+|(7)|P{0_-2!W_-$Tdo;;ZRG4x z%CihYBq6kf8EDEqNG(<}w+!IBxkZSVEke0$5wc~A&@5YoVA&$n$~GZY>~eH^#wWKO zW(m2g&{@fCBnr%JBwdr+NI;mmam4p;8$)IDVjNA{@ZeoFrjKxcU4820g zHmc_apPz*bE$F_)^S7u!_Tci+*ezK3gJYLGGz+npJ*ox=PhRrM&*!}I%06{+&MCDg z@vB*vJ^54I5E*-B?3H~FA>e{~11|fhKfLnnoMVaKyVXwke}QKGE6+ajsya0I+9fDq z&hv9dbB?J!uRJy9$edks{?LlME!{tzZneM&oa`uaBOmwG0is_(%$Li+L0F8nW?d=} zTDPP+j-(cj4W7D0J@v}db6$WUyL|PExC6Qcyve~oUh*RWIz7iEl#jjg+?+qE<3ABt z-@sS(B(hF_p5UszuRMik(1G6%XMd>qnUjhipGE0EPdx9&V;`b}bB57_sRh#3Bk2C&hoxmN%sIi9?X~+~?ZN2Z0!AKSdF9#ZZ_F7+d8aN( zRep*f4FjOTS1)= ztGwnh(Gw}4nKP1Au1p!2h_=qTpB0YNIRgO1AwtqM`c@<<9b_{9o3zb2`O#%trvx5dQEzOZx@n6;}K71!lf`+ zOd|zozk_b_LIjc(<24^ow|ez;qKnLtRt)#M@dw!P}M zs}L|4W>YO5(0pmmEDcHE%#R{omjz$D)0_V1ZlFbw!aR7$==^gpBJhH_;Yt6qeQM#v zNdNN&{RNj>ZDl^3T{SI-)2k-sBGmZtM>RngvlcY)JP-jeB#3!s1nQSnm3XJbN8b5D z(zvRIjNqy$Xyr$tj+w4%fI4Qns>y6=A}J^hap12pukZrbtoEe;&YNCD0a3mwN2jnU zN2g*_j!q?JbrHUmo7G`FB4%|l9+hTw2_7|OH3e1drwFDX69f^iM3||FA8HV0(n5r5 z5oY=#gsEI64Z<%V$8Qmg-#!lQsl5=NJc22-Tc#AxmRgV$IGFpOBHB1``F1=RtB)bb ztIwejy&TNF7vB#38U(PNO9V1}W9L!h3vivrL&&`shZK~O@1Ty{0|PgGdb|MeD2NPs zO5qqwbqIwscR=J=AV-B_0hbNO(LiEKB!h_C}jc%RUhi0T6Wi{&S|}AZ)zB z2$tkzeFT^>m06`zR955MkHI%~YOP4eoDOF9vUAzBT$$|NM-S~n*S?8<;RO(BzS=U8 zKO_44Xus+d1V(i!ggP)gJz zbS_o_#Cwi_jxsk+;3{7DgzZIE3$11yT?u(0Dp=@gWvpW}5wYR)GR#@6j&aB_lO!Fek(s{dJX|Fh)ubyP07b=RD2$q9~ldrR)B z;2BG9e6?Zm>1%fXe_3*w{Y*Bq|EISw@P0szb5{L7zJrxCoozXtWe4MaO(H>dO9>Ta zcCgn9u$->o`NyFHxSBM{@s?K;_Ac4OvNka6T-?BLwKQu3)5XfxnEvnCz?}6|LVho) z+vT;5yl+d=HdhR8f;iQuzKTxcplMSFUT}lKzCOAF?qc_7K4M{z7Yb-`MgfKB*3BrX4^EgwEFiBAvLLnJRRLJ&@U;mqskIY?g z2KiW!As@Ba;&k#+atS>#0vi>Ycx)w+((1w7fOQM6=}?a2>kbsq;7vrUqCZ`r*8btT^_<8QqgZp_S z-`dk4>rDg27QY}Ie~y$F7x&XLYM}WV5dk(!rc;IH17rheF1r;F0g@_CHa17o#h6bK zkcd$8s^M#seucVMGwD1{5<{mQ9h8O&QZwq3`w_N^d*5w1 zk0GDFE!B62{U*85TH#6L5BzgX!Dr_XlDI#@uCB$`02-5Mi0oc zmF?l?T=QCwtlTR+Sh!f@&JzDGS;Cxn=oI{BFJbS0@e-EF<}BgyzWIxnFpe&INf*B3 zOIS`tM2npBdcpjKOZfd*!r*^cxpk8+VKQ+raYm=fttl^-^`m+j`zKk(0$pYqPo?mS z45MX4D-e5;VYCvP{Xs&HJ7h+jsMU($igBG`NSpy#~tksE| zX+VP95b!y0SOcE}+>d6nrIzy@zb(zyCocGN7j7#XE~x6iX5rfWCujW@(3RIQEMmXD z(-bwekOwI+Q<$2IxYT10n%#m9RSU@NuQw_L4o02j*8HTWz_6FANCv#3nqTA{bFr_8aZa#MM8!B-W_61B$b)=@&!478c9yyXlk|66XaoQY5&RMq&h4(@2Nl zdKzx`YQm>1GzMDH+~jl=Yi$s?A^MTf@Fd`O1glZSM79O z2+R<4UbWMCi*)CKykh5}0!>1p_o_7kh2E>yHdLQjm(st7w~;;e#&N!TLP9TRan20T`FOvLG!8fR8T^sI8rtZItTG8c!SsgicN zIIEg!0HHmr!0d&6&Zejtfzn-r8L2UwnoN|?)C@~KW)tpUL8%Vc*P&F0>+4XeTPe^X zTT`>cSxb0~&?upqonoosA`c&IV$v@WT27W_}$$UBWM*tMCORWQ7;=x&)M! z6@*X;;js07Vnv(WMnl5*n_Ni>J!U%;mPq-@t;AAVM>U(S~ys<-A;)t5k2qw z-eW7ouWKy~zEgt`4x^Bl|OdVbQ^n^ae2;39ux ze7f&}lR0Cs{2dte4kHhw;4WBO3}l#7FTqK4 zSMUKo-TwYp_`dw9<#{lhV7R}=EQyh^WKF)hfZ^m-1uZZOX^dSO@lW@Q#q+VzujTna zi3+CU&oyP@KljAfDW`vTW;=U!TuIFD-X3gqe4liJf`Fc^tIH zTo;m0#vDygu3HH9MN%Vg=59r@2=(t89}M9k98pX5jRHTj9K{1)R^ z_isB(i;Z1Si@r{-$xp5+Xia{U)1zfq>u@dutbl`{`w7}uGvPh)mhEqhjenN@gnSbB zw;|dEg#^hi=r;&e{H#AqP`mkJTM zREWT(L4dsA4($mFWLra-yx8fX+WP)o4zsvUFTo)1K<;N{3STY_aX01;0HAP9l+vbk~T zohq|&=^k}x#7PNku-rfjL1omTg=`8N@!|9`fK4_+F+#zN94j=597~4XK`-LJh=SrS zXtST7U5bcxBkZ)(rthzynh&OtG+notrB!=DWtL+~o_RS|@BEgz?H*3{YosFW{~<}5 z$usw9c*&_T*1S8G<$ofqJ~nJ@0cIFW>~xns)pac6s!3pkRuTA=P&p}kp>a)~8d5K) zeK1brTC57Tuid{y9qW3JdQJU7mEXH|Z1pQ^_jjGA%C{$rwlhtl{6Uv`W%V(JRng|P zzgNFN>NnIcRrwu~`u9w|Ia#ndllteYkCnY}?KSaBB~SVG)lA?n->%M4?$%`SW&-Cf z-^|x!U_0)CXQ@(XcwsmP)`ShUvoTe^y=*vMsKRSs?z)&BHo7iQV=AlyKY%m(nd&?h zX5{MMCkBkN;m)+i(RDCjY+5_e9dpM@Z#L+%W6j^no}MrEpjD3YDC42E1FJuw7J6Pu z_xuV;KUE4F#FlniRo11QR>4bZUb1Mjcu1WNbb%_9#t)$%M3dm7dCKTX8V|wGW7*9{ z*SA})ZM}vjbo6}$&=7O?H!Aq`K7z_-rsliLPOf=(>Wc>b8J%19=o&*krPB1TGX%)H zj;`I`c@F$}()O_7nm|}|1XJFh5ck0uWGZlOS+wYe++;L3>Q#Oiu2u5_WBb%`IP)4? zGt+iRk~I2IFiL%y`eey>4;$N%-KD0e66L)kS$>B)Z|*>HTIm(Wnv2Ds>D4Yk=%rhw zz!tV{X4w@+*O6=}$~p>@9l_3N@N9xA^J8QC04&}9u)T|y%pEALfJ3G=@74XwfdSn@ zPfedv#Sf$cUuLNvDXTELev@6QcsXrC`~D@vn%B$D`RYuouGY)VcSsj1_RI6)>dIY|UV%E=Z6yHVY zxZfu~Z>;gDKmwaxpobYB={k`OW?55VvT3~Wi>2|`?DRTxMjgDluLKB`27J*Fi`8XA zjnI?xOP$A2=Md^#V*RbIleA*8(@-}XFtWXIE1Hm9n|f;lJ|aVYKF2TV?NjIHc-hGq z^bvk3E8J1*VIQXQAKZ!EFfo41Xkv{Q zMp{8|s0Fnv+;!GH`08Hc#aDjmJi_}v8O0;JmZ6G|^3HDE$5Iu=L#`?Pb)`cUA9t-V zhAK*?;ND(&o%QJVWpM4Bq|WaB7tkeDVey*6jbBAea-yMlDQruveL6l1#=w(Nuc`nH zQLBIu!lwj0-$f044aiqRT?zQ>tUsRXe03VF_y3-wJh6*cGJg~`_5rD=g<+@NKA8M`DEaw9>)*c%k58nW z${vBUXrZbWH`;U+y6;U%t0fc2Dt+vzv#GtB_Od79UiYe%z-`fXWrEnt1F^RXC{U%# z4#tD2n*%?Yf|jW#t%Mg%9}YlS^+o`wcA?(UDL^Gv;RzGCCW0bCJyx>F2nuwg!1uGj zJ!Ma{UK5`oq}~#%5*nDyFB6uSs;U-4<4smYF;dBe4ar#3P{i*-Cni5@s8~3;0{ix;%Y+dXWLH_dwfVfK2P(kRh?!>jM8!o$Ndh z9Gg_bj@-JG_jc>kT{x`!v7l46J948b?Unga#ICg9i?&ofWUT+ zD(-&{P<&i8knzji=9)@iKpj2fu`>{UL4+x>}~J+96?BuUQqB<6d@)Xkp3f1jFD z_D~Q-zWB|=e7}3NVzSIzkXg<5mZ9*e!}Bq+fZ%5Dc-{wj%zg2j?)hH#d_RV$0PxlE zS#SM_?qUETTB7lYG-5+FXFQaP(LHbrv)B3=Y%WdGJ(>QYO8eK+QLi7Jq&p~NPs%;& zMx*RT@AdB*p1Kd(YT^@-e%uR^jqp_9P2OmvwBLCcz_ex)nlyNCYX^M2&#@V?O0sb3 zqKjkj?z7vE__g0gJX~}a^WFtam3QZ=+JuoZ5`es*v$C|t-*w^I5$W2ToDsT@yWvJ@ zV?kn-^eawaWmKDT}h3Bf6VcNjGN~@en)P zUE_7b^B=UO6RSONztDhXm&h3#zC!1^!sFY~)mJ=s1Y7-wQVo3{9nI7GAN?C*98yMi zqATHt6EbO)Z*46KO$@rrp6oi9D06jwOf4wMSrF>{V95f%ulIvEZDIJ=4y*|z2F8-} zy($LeS8rW14Vi#_S9{BoTJrv$BZb-vx>YH!rndpR;QMf^+dn+@wIY#nHaiRu$B zoZjljehw;OsZq9+BbYPbj9?)~urRsUNbK?^ml_JUx~@CNo&iy0Nvj1$*#bDorcd%= z_e&Sr10O=R5L}MKMRan3abyaz>JK{Wg|nR;_fuo`_8V^SpWutE*en=5yC{-*%d$79^TW8P5 z#7TE&6!Q@F4vk4>%^P2OD>LtYyK+uGP8l`7B$4CJnIFQap)MI(UoXvA*u%S!`|A-h z5Rnw=;$cY>KR5gP==s1Tv~MSoK)TrS>aFQ$6y`+MnmhH1Gvv-bGMOjh>^@v*2tu*( zK>$FUSh2c={c`oha+>xSLgQ)%>D z)}FJb%y!*8vBT&9=8VgRS^LX30Nq6V8GIA`smpfo=TK$nd^dK&Ffr(U00$!l=evLn z^L@QEKP8*b)t=6UsoEsAq<*Z;(gpsmSvHcy{&Nm@z&hNi#!Sw)ccBGFa)Gl8fpI^+ znb;dqL#iPO%)8krt=;TT6>QEb&p0QF^4xZLYJsl> zN7s(9S6o;?M4TnAPOqvd$*IALDg+)ZU4pLpA_9rk{wZp&ulHR@<&GNI?ZWkz_0rgw zjD^ERn+ySJOqPuRzX;lRLbePNY@3J#gFr{Yadb2fU+H9B(9v9tj=Dl&jf-RyxTy;+ zg6CTh0b_JhQCvr~QwYP*aGQ&bk%*`Q3z6i(c^TRw2QH4SRo7)mi|j!iA5m<5sE*`? zj6W`^@mMwjW9CQhCx(tvMIX|gCvn$pqBq^idgk=4q1I}|+MB#*swN`pYJ+Q3A3YB^7jfq!4r~-v6|Ik2McmDR-bgc$o*={*kS?jW zm(ygpjXr||$qIwJ(n9!$$}Ckbs8?UmQY03)7vZ;C+(g9V0M>g(NqcbKEc+sN*}TKf zE~^2nvfZ_}wg}ptfJSh<00oU+m5vg=1XC+rfaI%PE%TKHcxmx2Ta%Qp0@8MZ1q!tz?YYO4D3j0Z(iiMKV z5KfMAQ29EUzh58c{bpF}Lb9W0>4D3`$?93*s$T))>3jYIwi|3BTMf<~NqR7R*m5zh z$r``Inb#CQPvKxwzgybTs7mc6xndUfZcv(gtZTjqG0=FOib z1q|Jpmx)_T(A!5eOE_{bR9o{L)rh*Hl zmgpkqncm3|>hljCfvo1#^y~8wfg=!psSNn`iowZk@Oc#T>R(pO|3LHKG*WP8hs_%} zUW~H5?ctw%+~^_ie%Qx0o$?Ni9Jth@=Ptz|yzrNWVjOu-*q-maW6CL+s(C4E%G)#3 zR-wfWQCS1p$*Ar;m~#3VmPJ|%dveoGxGQ;2s6F&PUT@oLD-M<1*O0k04aM(Ne_P#3ma)=ZRr+dIz)t86FS7{57A?A z9n1})=MIcuC8N>2hzS5Fg_8j8QhM>uN)P7dGvQ!n$s5uWgxMpZd>{xWW{q+o8#}I) z1%ea%0}%lH3JyTnj|2t45FVD?^DD*xM|l&62L=y&rVlD_h|irl1Hd3T17P=Kz!2>W zYSz#ShU+=%0$D~afcry!gy!xqZ3#54xlDaGl+RaeZvLF%B~fesT_PtS-$7{RUGBti zu&gD}btqBgb*n1xCvrf7b><|iyoojNKiIp0bmoRo0ZQpEozr<$6f{zxy%60HwVt~Z zKAOQ^sj~|12JT-7&y%nL2DTW^U*P@O*;2isAaaAv>8@)hm`G7jKYG3sZ%0Zw4J;94 zBMPdwh5@S-YDlF~YsYuE30`{*j6b26>^{iel~!D^q5wPQ$Y?wlVcmK=-UWHsY_b7JW>n?FiQ{{PC5}tmcEQi6g??X0k2MBj6M>)=L$BNF z@hy?zsy5i_#ruIvp=kgk@G$vciOE7}Z#KYZ@EqQuYl6tIrFp1oncHTnYbW4r4`tO6 z5n)$~2pj5vesd|5wQ#u^%3As6QYdTTYIC!S;uz7kJ`&0_C&Q1U=?n1g;kiQl#y+L? zLUxu9*D>jwR%%EUrHpHG26#LeIe``~3ev|!rF8&Jipeo3RSjlGZD^&n^$kq)JUAW2 zI&Dh~cp)i>lS!!Nv79!DtPmen^Za;QCU5ys$fw{nF9ss?E}m}o2uBYY1M%F_tNp7l zp2)Yx!dOvAot}l58Qr%OxKiG`Qr>=4;>TYQ`9ksBA8}07g`eBsm1CkdkYQlC7KxuA zS_iQ{@;J?!J37qm3g)73ywQ@PsD35V*g)@t* z5{oO<6dXp^Lb+l-<_aG?&~=M>n0)po-3d1N)dG)}V3zG&?OoxHEw)~35%C9z+Op&9 zkToCAPV=smaZZ^QO1Yzpt-VN$8B~zi?JXNx?WcV5{2{~t1Ub7yT`wlDH!zH3R|i;> zZ(|BDK=z5S`lGtVQ2)#Kz2iA^Q{G1iqN?`i3@VS6JdW^5)fiIM!FovBO7B1=v(yw+ zf`i197;)yOAqq%c>-?Nl;1hcNZ|X=z@+>rj<*b3((20Et#qi8ikcxpR5BH52=zR2j zJBArk%{ufFrYeR4np+VAMtQ8O{{W^AaS4SkJW`C~tKmHMyBt|oU2-PY3PO#HDC^?S zygAA$6qNNJ%f?ZbO*!j_ncD;nK{fc0EctxFq2f?Iam0m5AX+sM5BRXIp-8|D;nWd# z=S3O^fWP=|#GCX*tdWHTClvp?bvza4xDe76E_?mgO_#c*B& z`pCMSJ&&E5$KCHREUps-5Fm5IjZ8K^zc_n85tOd`=h?rN{?Rx!y9L7)!nhcAbGU%) zPnSQUS_8*;jI}2@O^&w{Q$YNGN-Yxn>`|@Wv8UmYxKLF3G`~EVXQNLhnu+P+O$ZZgAvB#b7p3G_u;SrUIXpt1GEV0FgtCxNK}69MoRx5sj=X)rIi-InS&8 zj*{NXp_?!o90TzP#1@3p$EbN&Zll9XE$usyx}2|5gyAod5x9v2wN#Q^BpAIgj}8uz z*(eud%tpqO{>t}l6z&<^;zDqsDX(%>{nF{+31roP;~ju&nkdd@ATt|Z7n*Pue)H46 z(8{(RAtdvm$wWHd_Oh3I6Hki)sy9(`xiVCM^TPs7-O@)peY}w>4z#wI?-GoV@A|*2 z|Iy>pSTw`85oUQr1gm>KqyJD2cW8M z!4w)73=vyD&&%{q}A0OmTAX#CrN!5Aiq`d%J^ydav5;qm(ye$6>)`N<2hH@Suk;r187O4-CA+!DfN-H$$4|5Fla>~6~FDL)R{7eWcj-RoFlI)uo=rj5vnk$*wR-J7_uFes`kVuiPV5Yz z*3NG*5B4L9Hp61V2-?Z!MYNqo-*uc{5MHWz#$t>)rdd#w--6BzYg9LSa)xDwj8BHt z{ISGI&J4eA!?m1ByIva{os!rY$vK!R`UlP?%py*q;hZ6zi#oJO&)(yt3^+II^&AOh z;e>IsF*kGiNK-I<-pSIYypx5^3%rA+ai)x7ru>961p;u4M^V}6>MvjrFmZ}fwg2L} z?2%OMdPD8A_4kzfoveN^0F!K;-{B}>%7g(z@%WUv)|oR7?&Z_r=M*m{eUYQ0Au_bp zV>k#N*R&jTSqimmBo%W*VQui&M&j4UQ{D^9YN(x9L+!+Hy^00Y9%f=0Ai!8awLdZJ z5z4x>+JLs@%0}yndf}q*usFzuPX%uyiI=OrP2-5(6G!v_eor zMd-W`I4>J7=C=g~3|y)6F}ZMQ(!l3pw6_=z1H+J#@!lPRC1xI&`G^mvqJmf%O-CY+ ziHZYMl3}Eaw|vI0=@BJBl^MUhTWuR566NfC--Zz^id6Kq!RM#s{5-X=FjfBdTqS13 zQSh`YEV-LjaooPx2kW^n32!D1x+eoP5HW};CM};ce&TJofH9-MfS7X?B~%9n3r0=EP1v4|tn!#~8PaF{=wX4a}z3 zG1n2wPwWbUY%Rjo!IPZ&zO^~vej}mHBs;6(rVz7m!3RbO{_;n^nOWkeokup~JVGTt z4~`WR@c|dxRW0r!!!uQ=m9s!c*$pr%UUK*SrT)#E8v(S;0|)5zZM(4??RUG1Ot^ zm(>+_9f9q>({l^I)T8Vw`uCcf0D@j2g_IdoT()IH}T6!8Z`5>xQ0tB>+bS-<3c?_bRt5{ywv3 zyS&0(!Tl0|y8;r<^bgTPtq!;=7(E|u6A-LR)xh<4P0*TQygEjD{|2LL-8B>oJLkV| zpn9IaP282|O@d-#yRGoklDWY&YAu-rdc5^Wz1HKJSfE;WuS?A@wf)+(<{xqtJ$(Vtx0M=s0lC%*AxVjv9ziWV6CEbm&)z z!PRl3D9qSBxhW3^!efxp;%X7p%#fl8(}_wfA)IvzNi8S)Hv<4T_~h^QJMfz;5v#n& zpdu@iHPOUCGzpe|hbJkKbq^zv%e}o9zvlb`Zsr9cbw1spSB5I1bwXe)@Fv8C$5`jA z?uRIEadn=Xc&M9!c&;^b)*EB)>my%#90G>Y4#T(hJ;49j+7<@%y>JPGtig;vHt}Uu z32uS{Z0{=QHH3wLQM1r{svaD$s$zs7c%RblO<|>TbuZ+JJnCiKNv?jxwKx2=*UxM7 z<@ZzaGS0ISHJ8Lu4|wk%rQoD7ifgLRWvftbzsc5xBw_E(C^IH*DTGjoZt!ePndclO zN@#?*gMdXeXLTk=ffA_jn%Cce#;lWP?fy7qGa$dp7EzOAT=6nH-y*|F4svdxQMT~x zNf#Q>XWs?F(z7cYoQytacCiZ#fV1+AH+a?BpSYzM&3WQ1&B>pb1{}bwDj%)s{%^va39uvl3NU!d1nXshF5KgVCHPt$+VG&UB;h z#2-4=?qB^Rw!QKBttv`nLiZf?QqQfTxN}K6{PB`&?x%#uV_kLIsj>Ec3;^B?>+ucP z>1OQ162fL$?`{7oDDd_Ozk+aywl@@ib6h-kC@pL4ns5sZd^|>0=U7it^>yROBe_}AORrsd>q-eUt6MuH(57yUOJJG0b(I9#Rk90#)@04f zmP}`4jVaFSAQbfM--}ryoS`ML#pvvSwF%dX!LHiBCWt#mL4Cif@?Txh*1_j|tK$ba zJYn;CvFixL)UCxn@pFyK88=`?e9*}G4-R0!8bLA@gN%zzY#?guK}MheC;)`k7-fUE zRH!PW`@uA-pW2y&TT_tHs_qBzs%9pJgyS3p(Nv9jRb8wWIrHiXYg37ySHJH_K>Ynk zM_Fre*OyU^-|7V9;GeaUD>2~)k-pzj`uXBDpCyg4xRDa|aL#aPjR8>~)Tpljcfb1~ z2bLO-x>W#yZv49v1CeOGS{#J_;+bT0Mx87&l8V89N$s)`QomxwO%8NRCb>` z=i3GfJ7KjY?AuXQD_ax{A6FIA98@tbFTtB!YSTWhS{PzWeN{qLw9Z?OvnC$3T8@KI z)VzY)8V#ng_Fh=oMsZi;mAp6q`l^M!1N#v;FeBY1IvtgrP>ix5&mEoS+c98g;mSdl z8Z!^Oi9cG;QH#{orC>OwAQZK}4LMh0cL8*Cn_TgatC*jcRjPuhplAiPctg>|&PohZ z9jKEcEG8FEzBr^8Tbob3ft+4V=I)YRTDUPlN(C^2=v)6yq)tCgnk4t8#p^e*(+~cl z`_$BfCfNT6)1n$1_jjMli%+M_C7dkZ2&LEciV2a-*XzTkjL*JadJfP@3Yf&MQkU)S zjI3X$%PvRJ2h;J3%JbqEq6$#S%9AX=H4!n~c9u+RL3S)`gbSGN&X1XoI^5^WaR%_g zMGVeEk6D4gd2Ot_#Kr7@)F1z1vV473u?K&lK`5UZUx3Q1*Cz-N-sa%#678rg_vT_J z`^3zMMIAK;e*6=^(7anFPeFEt&JqW~Wh|Nd`_lF{&A%s2*%bN{dqB*|kf*0E?k>M*O1v3^5dX060dZW3?IWnp z%XzfB)YUngL!5gU1<}q*g!5>~?Y1uYCa02DRcCWyj7F=vBEyBrQ?ruB`b4!s)!qP> zQ5hiL5CiFm0fHhFVm->^d$^7|Hvx6d;IH$DvI1uBv?$2PB`}xH`;f83Ug(TqRtFNV z$tLLAz%|sEy7`S19KOywX_9gz>yX#p?GWB<(L(M`!d(O*OX0%2*Z)-Nb5KNn>WlDz zhvZYrpj?;~6|S$ho=aXb?jF#p-TIsj(ZX>kUVfye2sKyWGT(Kk+-<|NSuU$DV`x zzu;VXN6%GqA2)#WEO3Drq_uYmuxaVdhHoPev1&u87^SJtf;XnTKeSI&m4*JCN7QrX zMJN;sA~+(LpN4`~zWHenbwobx#kWY1A1^ZDmX6;F8}OxD@QWk;!1xl zB(FG_mB=eSh0wWPPr1An$t%3c@fH0b`e?8sR|9u81OYvULtP}F&-`>vfq3N0r0wZo zF5pn%%pnP=fjG-W5cB{}eiAIiX^RP)if|v>w_zHOI=#}HxG>%uDo00@br`^keqZXF zNT_}siXtvm{3SBRRiUcE}!Q2-L^{=+~Ip5!PxF{kEe5iQ>zEc@VKU~t5#4@xl*m{_ozU>YVJ3vtAY(( z72ztQS{XKY#c^fWll-hF86cB3c;sg&P%SZ1a!I2%R6+=##U3%*Ika_9T)!`o!y&H4 zm&EP>l>6*5#TfoA;hPU12hJF6nzMkD&oi-s3 zvH=SX^pZ+nhBaN5hGr04E&n}0NqMhXk~2c|o}V)U7Ei#V=0Ua0SpOBGa7>!+SEt4= z?0z*bJ`)4EzT4h`jPktBN;NAr&z+o#Tt)6wpk5VkR$ZG7)x6o03T#gPm0IaGk}$-w z((S=T*kmy;Tm&%VdCbpGHoHAQ!3Ctj2EAo129=e38|eMH_1X!}&#@^bj^f58au>)p zq%P6Py_pu|jO2_g$pGI@MT;We1OFm(<~+8c{IC3-O@QC`K)0Utet&0827JQ4a14Zvu)fNS3<+ej&g|Lvi$JaC@?wfJuN0L1Wiq-%0Ft)YoPISqy5o zsn)k~54sho!9$(W`8 zKHmkjB0cA)3>i6o7`DQe2IFs&SyHTmfx9+1#Wi8av1%3VI97Ghjw6si>~g8A^R2%! zaKh#509pq*f+gl(Tbj$Rt@UlF;UMXqKvN73eHxY@j zE!a~z!<-6i?nTqfz$2nLl?H@hLxuZd9P^O z!nni;4hFHtA>LR6Vu1R|OrquQhQdgNh=2;`Pz5ZndsP8?L(t}rLvXq4QjJ`tAf*fH zz}^tFSs$&ly4C<~-llg3A5bN5V|_G5Dq2sM0$vqNBX;e6--fm5wY9(Z_5LfK@KbLi z$t4ECaRF0$mss8sJ#(%w)DmET2o%(k2Fmex>E!gzFe1pmEmY48 z{yI0+FfEKb&DdkG^Gx&o^cmmx9`!vC&BLdhJ*moR;n7^M=BtflHE&$xqfZLat^}qM zSl)8$lpV6+RbtBC5OLJ{+R*t>Tj=*dv6tPr%Z^9fkjv#nI;5#QNxWg6Ee4zW#1U9i z$_s47S$<7;7}qhOby$s3_@Gi&_fVDJt@-?#!w>ynFErx5h0Q|kp$endWtC%ml2zry zRS{+crK!|QIFtyFp=?lWV(fuUjKLKh(MD_0$JlvZQOL7d5?IO*0hF&D8Ju{ zdr&fEN`{}BtSry;P+m_Kb z983#4aguDx*~_&&IxV%4tR{xbPWTf4#_@8M!S>ke zs`2%HBG(p1EK!%sC6MUYvzZPH4!h$wLuy>%Z3bRHO*R3YSVMLe&Z8!E-|IjNQOdq3 z@d96~1nuueVKu>2;6MC$p~gx1%(7KQH%v{g3aY`;X{rRHE;=W~7WgZ{<_YN-{v!I2 zP4;Rx8Dn<&D7WTqmdny=vPz?2mm0v;5NZHy*tjXs2WZ7J%Q}i~n66A1<BF>d#V% zNu!VgdmnukNWNh*t50&w2jW0ZeGq|SqM|~7J_aM2S~LbzaM&s^M_J2b(m(uyT?c7fDi%r%g_8ld$}6k#e`yv#ZBp}Af^!Hpn!2dvy%~GTZZ9yE&$lBs z!?&a5Tpn&d_{^`L`AuRt2q%p(&t=ab+WX9JlGO*2jfXEAO@1K_Hs@@le>Xl9Jad?d zbE*P4vHYACf3o^0K=dbDtz_eIoEV_M5D=qjzT{^(-Owlua)F*AHU@k)TLx8&X_962 zMN34W69fjRTGWuZ(NAHX@gp>OQ3I%hND(G@c|E5-uK+aH!_vmS@r0ugkvYe_XU{Zp zHU8-gLtp5`2nr16z&OB~Ph=p?lHEicNSoNL8QBdsfO0O&aGf<{wj0ND9?zN~oBCWg zOtCC?7ToySxM10oCA6Ja|7Boq@t z3&J^rRFpC~L+phTzyTA()N0(R*Ve6tM>&1(483y~I z`l#S)Xq>kV9-hW#H{s^HVCv3upli>KYV_(!>n?Qa4x*r7rp>Cl1GmO=p#ozYh$2g* zNNTOOysS1y^*@MWA^Ni(KlOSBgJl|a4JC5-l8X2+7RhO)&)@|!%)-jLvB~BG8i{Lb zwmD%TTkF(uEOFR~UNH;U2lcw@Not-q7o?^r;-IkN`m+=kK1*SR6AOFfBb1l4CU%#D zp5s2`{6o)BW(v)b_Twk=V7m%S3*4X1(Kqy!czeIvjZMM|AAgPZ>orh|V?YIkus^t! zU8SzCZL9|-DzdjRAkvs%Dvd;^6N#PzvMm6JX_I!Nq}|X-+XP}rHR4PN z304HW+)}H%w9DPP_qz9XyLN9^w7NC{OHfb&yLCo|QoFky zjJ4Qy5tU2s`*WUWCX;}QyZ7GL>-YLCuNRr;dCvcHzUO?u=li#aQuzoPM-Kc?<^Cl2 zPu<}y4GjL0Cys^$gk`;hon6AR9=T6wz7hTyvQj-NrdmW+R*9@^OypW%)Nv9(s7qJf%vaGn+gcJT8H@CgH|MmM)Z!lyg&efc)W?B~n2)w?V2We~3kGHH0t3=RDv^6Qt z5|(Y3aSfG{&Ep|?0lgRNNo55X=e61G3+=u{w!JOWnV=JOK$lcf73@<=)8TA82Az&1 zOKP3+!i#)~E7jsf^cI$Z4>hl({W$&O+ESJm$J(up2|R%uEyu*qHfvwG;Y;x zO>qm$m%K4-zn@mI{5>Dxn*&yN9;E~0VD*=l5xxa>-f8+deUH~CSAWvw4>NU|DtJ4= zYVmfYn+$8`S#91ML-vA*eLFtWyj;7t-bX%7q7G{SinB7#w2sRYH|`*V9lFc@LdAQY z-Oqja-#NRdV`r%#KS!+0JI(Gcx9q=db{jXo&FnHo;yyU@ruo{Ob!SzOsFSqGTRDua zTi+%2MazetD-UAXmPmUy$@9#mX^jUo6tNq;LV zLw;qeV2K&sjM4Mt1ZthZ?yFj8GUnVV$P*Iya0GHmEIvI$e;?$-J6)a7r60&&lfrCs zY7O!~;wog|GW8(f8nQwgF*2Ov6)&=M%26sE2lGepeQM7~Jh0Ay`3#Ox1L^a9{Xa0f zy6ZEl!rsNP6=VaeBH!66TCIxa`0~>2i%dmmNnR7={98n?#I{W&u;a`2@23khPyDc+ z;U*yu7I8eYKbDGf(>ULplx^`nf%WxVD-o{tKV~}OFn%Vm%|C&&g#z2WqQEwf7DCnF zD_rtRC)yfkD00j~96}=f62QZWh6L;TY=Ly)gX4mnK)Ps@7s8kks7ws=>H@>Orob?- zoy0J=*Z2yZhpkoOc7EO}Ih*=2P94s@^Wyo_eR^E*q3#U8;rWjbU_$T@X8>pL{NKwE z8ez(>z$e4E*KY5y&rCstgHD^RsxNg2Z&bhk2R02L>2Z_CxN?>x>0 zK(S_2vgMlGr8Bvm2)co7)Qgg>?YT!@JUzxHm!O(0zLFo2==H&wPXN&I;qQzZLw9#vpLqKAHdJS!!w6a3Ywv6&G$i*nNvvIkBaVO z+&Mo2=_9K0s;G$Eq?CuS3TVaNvDIE6wq`8`#VgaBW80XOWTKs8qYKC?$^ECF>O_sA zs@w*T^v|3+5K56%y-Ct@b>s*|LP~q54Hh>JPK|?BvQsYc49khl8o-)FUQ8gd+yeLC*nX$AQf^JKx&$>O&3MsY2p?hMW7~3#Z3y7GC2UG;6^CH)}5B zyWp2UB^5sx9H`Cw!d%1sIP8G9S>ZZpo*$ZH?na}Mb6K!Ej6H`=VB5dR!YR263;5L$ zQ9j(epQWW}8>^H+EQLghJ8ZM=5$@{I_cYg2D8#(TB#i6wy=(C;vcfCPuy8UL{vF{P zKUA3mAAP^i0F1fN( zyQ?`N9X=@O4|wC`>HRr*oniM?7UTu)NX{ry3OK8+-ox01$tom-8sJI%)b65v>?(3E zJ;zc3_?m0{iw~K7^3{y=g1JaGRmYoZ1A|ww%#1IXY|pKGh7@QLm|^HsZjNo$nggC6 z0_1M_`PkT|#@NSL?m-*~2Lv2kMUzGz0YfWV8rUe+YrCo5Mo!U=XrRd}t&Fv7aO#gz z|A!{kzr(G6CiP#W`e6)J4GwiZaO?p=c7YKy6YxZWRC_b^M!w5QFV53wi-q95l3_v*tmd?2A1+Zc#g@VrG{0jb~ytE zW82G_7N&roB*)1%ewi1|iazSjV0qO4JCdzNCjSRViw5>P46JBWkKp`r;;8PSx=EwzshBV#6y}{))BZb( zj_y^Lo8#V@mB7XphC89yvCA|8gE6Opo+>9XF;{`K0I<$jQ} zTaXhGt2x{z>X`vA(IxFzWtL z4ZjW6SAjg%F9w}YQYLQ8uz^`zebENJZWJ*dA(_Xg{lFx2P=z?!2+aUCKif{;3 z+YJk|bQXG{#!fDB&1aNSD-#+%*S)J5&55f*rBqkN{J|$pXfIhwA(0BJLA>WGE4ip^ z0YG%4zb^)Tp|1Gv85lmQ^)g$oaeV=TZuIxQDC@WzeajD!F5gEL^Aou%@w|lHt=+LQ zIlndc+yA8FJ~HYdn=+T%^--{PM8r=d+0kmejnJ*Kt^kGL4JIX_e)XTRl)EbA1^sla zgj>>@(AQd|ZO%lEbzjo|L&q>DnEGKqt(SC7WmDYpbGW#7tfX%7dTSV1ZE7T8iSbp3 ziUb-E)^)BP^CbSL;fSL|kHASc3%xP^*x`p zfAytbu5$G2Wld4x_5A4Fb#VO*Z{D22RK&t&R8S zsOj)qTes^WV9cFVz}Pw|D`o}P;ZZ+5u&ciu+N;tgD>6?v^3O@5~?ZWV{1*yWhTgB}$1; z3k&CNHKg6Q-IMRr+vVa5{2_wcz_|9(|(81|FAwg*xj z48FLDW3%`$fj>L&N+WbY|7ZFYiA^V;kri}byuy>KT!Su}=F;IVJ? zKLi6-x0OczH}k*cA(He#4AHikL*T^vRvs7ee<|0FMqs@}0zH5F*6Ma5?Qqo!3|bU| zdO(K02uM2oq6u9V(|cnqHZW}V#xe-5^yb+2DX&wKlqzvld9Kdgpz=BR{09W=uZTnY zkf07R#pC=^OdOPK4zg!I@BAnug}2A{@?ex)e(dDeX-P3L8~EAl{QQ&ibJY2HQ9s*b z;xA;U_z`r|2efDIzl6k{XWb-~wX;;@MoL|Yk+(P3G5}gFs#70U6X_s&lJ$X61M0+~@LKsx0hVWSB zg?Hn!l)C&5H;J;L(f7ggbYy;DC6x-f0^Ufw{QJa` z7oOw481qJb7OAz~ZIRrN**w?fK7Tc?DJ3Gc=hiW}MJziv+yp=9*fDB7G5+~H_A_u% zpUXsiuH?J^@hF0%Uc~3$pk+g)3)A3tk`lS?njdyK*Gk8ri2)ih5d9A_z&y*0LO9Pe zMr_ga93(Id7M?n{-D++#(lCdj=PIFmfsKDj-S$b0?Y4plTWY<|BzZ<~As>=_$ug82 zQz6cCR}|L0rhAq#UFx1UDZTqMP=D`^UYfidxL$ik!HVy4wiDZmo(6Tt!I)2X92DA@ z>+ph3a;3x0;nD1k;~pD~z2}DV!5~3uh`4g%X2Rx%wJQKq;I2075txpA2598I z)3B0ZR0}DoTn*sEPc6Yv_^C57lU71K)w39C2pUUU*z~FzxCnMtgLq&wJd^v$;bO#t z@Jrc+?2(%R2h2UVLZ-8*rJE{xt~4Z~s+~ZNLwuw|X;wqme>J?~VngGtzYrR)*ee3H zhf(H6m%CV%TL)7?=*AIqXu^~gNhk}#V~1+UODGGXtpY=rneb-OXUvA%v$MS"?v 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

$1 zXBO}?unO1*TmTAyZw0Qz69@oe04b0H7=UzO6|e_523!J6z-K^E)s@r(S^=GaZopt* z6tEE30PF+ufLp*Dz_FSuX#g|>IssuoEFcHQ0Skdl;3QxK-UH6xAdi3>;0JUEq`){} z2Jkbm3djWZ0LOqUKmqU`a7FoR0q#H-pgS-a7zNA%mH;zQckHT$TOzo-Wx#%tp$dR! zb__KD>@OPd(3+t$pao9>@_<4> z@WDkO2I2q%kO5=^g#h{JBJcv@fD9lHAfMm|Xn+hL4R1`!31|SnK0)lcC1!6!0WB_>p!REdf5C^0K*?_ey2d>!(n!K?SNNbfBgmx`2 zFpn-@9QOEPdBFvQS!u22n5-vZ%+7AWC;`*eiX39DRTKz4i9iDgn-hVc1rcO0cPkCrcKpWHPB%s8b8|TA5HHRH_ps zN}*OKNk|eVs#AnoMY3Kg(J9m_PnUjbJx9){L8TOy=G>)=R;G()G5wL8y9XnKk5I1x z-*|muqAZ1DsgoO`SjCIJs6%Nxsy zR+uVRB*>8mkf2~Jwo;NZ6lGGXQ>E%umDZEh7`qIiwpfBX%WF>r_gUR_MV-wdf;b@i zdTF--VHqNX!K`(obXZ1yc|pWY$bq;dzJa0*fG9u%h!Lg%xY`ivQX~m7p+egQB2S1% z7vTVA4iXX~?1GlV)f^#SBb$z7_TZEhbqY*>tyY$z!($GT6qQ0Xl=PBm)XdGU?rLE& z*SNxDiApjQ6>DittwxrhNL0wAp27;2OURlnS+CUz`M@-^Mw!T|a+wPCt2s);LzuFO;u>@Jl89a`b;wP_hc`DhMEgtm}6F7TNxg5y*lS zERF_#c>uGs@FWpyHLCDcI9QTH+x zbE9t1anV2IN%8P)lb}{5Du%X^Ck1jAzG)aMt*6|D1Vd(Gbb%>oU3A(AbwU#4Ep^q7U{QUfdt8{ z+M-lWi;HDd5BaPg4?sWfMg)fLM35Om1jb0TO%Iqo(WhY+_Cgpl17QY%rVrXR8gbzE zLc6i{E(}J02gLBpgc$`h1GEMf4)aUx?XJ=*l^%pN)!+ybiBgMkk?2zN%pgq_fF4K$ z!aMlD$(isXm7pthx1h&{m*}5NHS((A<&oJlVb6w{2jqY@Q(Hn?>vn0k=Rm%j59AaN zT8cCIM(QPl(w3>9y%N-DE1`_(CZSp-RL2jKCFq364iTm*baG*eEYU)PW+Y5FS(qpC zDr_OdcpyxJ7Gi@2(=Jk(Ql^7ORAB^y`bLZtXtNQr;^ZhFFHAC`B%(}OmI`_wx<{f) z6EZ2%A`;RH$6&?=7>RHMlo^y6aU+ zMUqUJ=7D^azDkcLrNas-+}x`ZynjYl+p&kYvyj3pUcL6 zd95gEv#1Qn(lY1>nCU2oRnUPl(8fdB@6h*(hz4REQ_M)hk^;})6Op53+)#ji zmz)e`&P|!*N!*+xhD|wAx^yY!boW3cU6+9YdWBLtFhMPq4U9_YtMwWfjeI4@1_pwU zTGfV){B3yU-NuGloA6|f5`&D57pmR6ooB+xkuLvLJ-6W6Fgh(cRHlnisAM5f>HetR z4nC|AdH+bYRIik=tTA8sh3HiYY%&Pp3Ok}NR&x%@^Klpg(c4~TBhrv z;bxM>{7bpRty3Wx!bNv5C)iLT^RwWNb0}9lcT7i^7-0RY5Xc0w0dgMt7p9S+OdRqD z=n#j^9gG`6599$pccGgRW}TOQZ61;V8Wx7^mANnzC1p{DgbV*q{VS~j^T(1%i9_%tw=H~5!tAD;VgO7p5WtTBB956z?#b0!9cJ%?glsM@Bz*e9q3Gn8gsCtjvYyRN zNd`34c4;ady^JR%>1kj$0P-YFOd7^LNlKD799jcKC1dVE)}EEnX6F+@Q|*AJ zia|{0HB;)k;&-SH^f9xn4wNs*+1zZ}arW-I2xXQRXonna~!s8-k%%X&g&Dt$7uaw))_AeW@HfLRCJ`pQnRT#3jv)L z>Q<#?ikS=J>3AL`A@ZdDvJ|v3n+hYtGPcH(Ct1~)tzWoGqlEglGz2DOs98oE&9yb3 zmoRm*dZ5D}GyOTcYokY>ip*Ik-*)II=AZH6@+dz%(K)WE)V-;cpI~IepM|k2W6`4L^rx&? z*6i#O_d3Y&hyPVpE?nKSIfHZ;^JXWD>66X7-7RJiD9~EsFG_u zR^ti`&n!>T=v&yXh?HsN8c6QWALCV5#E4bW3Xoju>gRvTTrd;G2nZ#SQ*)d9w!~ zFFopwy`4auhB6{NLxXkzjKh!*6`v0;xLF#wvR}IwLe5cso3x`f zn3>9=$yl#`;!I1rWb)tBHKGmj;GaGP>A)1j%w#5`1z7phVY0M%{yg6&f1YGz zV*M}6ni8s`)c%`QE~4ewgG79*o6!fVl}dg9g^n^+6cp}6*}fvPOg3CWLv|r=kOj7f zS5`LCxAL;VsfS@v)(!92371qK}cd<6(p zrv#5w=ulKPuO=i}n}FrAj9bTfCM!lJpmPPQ*b1U#-1;uKSNGrutOtT3A{gHwS?TZ) zr8WdS2u#esViN2Gsv_Y$989lX4KD!KS$ znWxbxv2VuB>*NwG7HLv!lfz$!&2x>8+j>^BEoiLI0)^Iem5TTjNlF@4gQ&L@NQ*Mf z()svu_vzAaMMa`iX?N(Om2_DW<}}AWghuw`SY&i(pu$jw3djS%c3C&y!_PpVI0vse`hgZ&SVs(8!dnnjd=66cC;-D?CbzlrdFd6On7Tl6W1y8c(Z}{)>3+Sv(E95i>%8g%j=o6*mxTNiG4F zFZ7A8)cp{(N(PP527&`asAH@0;(WF3%;J@lTdl$U2J{218k^Zt*$7MzN@y*0BpZoa z89FT9xSKy*M=qh^>PL?hEi|}CrNb@^yUWF-AgQD$bM4ah>-A!ZB$enSU|)%EavM<> zvQD2O8?KjO707qJ+;zf?Cs~0G1)*c@%@l)PrPs0wOqKk9*yU08UR)i1QSN0b zw3=GQ_JB|ZM$6UV7in>uOwVl=ah1tvx&D->R6~@Y9W-ohQmly>3xr5zoCFgRsYpms zYt@N5VXT5HWnjyeLTi0Q@Jf&AU$#l-i3Pk!lcFA)0&S*}V4L5v`tw7ZSgJ6X?I$;K zae-E6WL<=a>rPZek2{@8V%^0f#F72u#0{h_LU3l zq@$NtpC~6xAcW4$+c{1ihl`MrnvuAiK|!2|voj25{4pnYm9r~v@8IaNHQfr1| zS5H48b!6J8(PPGr8;_&PZLdN)-md*2d7~BAs+{l>v-jtUip=AA3hQ)lVK(nxDCSLe zq&I&hBxpHyy<}#A!Yc_v9^<&qgB64_BvzzgmbWy7YNkcyth)APa-HKLZ(jyaPF>MR(h{)2mX^rn? z()WTw9?9X7e;O|ipV*Ye(uW6G~wci z_D^fW{h;>8-S;+AuLYlKcdrBYf!izYHrYZe96Z?A+3kvm#9vN!m2RQ4hHmQl?J=Y` zrk14Rsx8#?c+Z@Db#I7>WUnFN{uWxvt76!YjK{chdo?fJZ7UtryS|WC$q|uGGka%A zx6(0V$G%)iFN#R}{A%V^Tj{u#zc}A`0e-b=RuVqgNM2-`Qx6|+o7v#nzZl^w%>^ly-n=K;S^X@HPvz@k7Y%QwL`>=@Q ztNUJhxSfV9Gkbp5^|FZk_{{U0<~wLV`NyNvt0O%hWm3Yn%#T80)4t#*ajf5O!yYZ)8g{`9U_w2F|2jLUi!zkv-=JR*(M^62V16l?xTHw5Y|Xifj&6>>MPYg zdOc>n`|Ose?_;-m57@MiUYcI_g5whOm*$UF9X9Wy!}N`OcKw9>fBWEMYp?zEQq6U5 zSC}DRJ!|AH((I=Wdmc>9nsHY|Hg4bGw0S@M-fqXj(Knt#{v3v3%b6}qR~~CP4k=%d5dxERn#JzdTj1>9HQ~{_VsNsr$|Izu0HWG^AKHMZ|s60XAX$So@3fg&kxbJdz>{V zb+1HZy8LruyTf$n3b*a$Vxg~Y`HXI&JxqJg$@y!j3(D_s=fnN2hv}l74O2I7MgF>Z z&i(z>VH%!MyT9ujl)uH39;xk*&}ZL&@)V~deM3P=*oY&vdxh%{roKdf+0|%zgPlj{ zJ%?dg+uyttk&h$X-@iRVH!U1h@NHwXr=p<4^-f1AnNAz+-~V1jL_Zxnn0AzgZn&dv zaIX;ZJ@xS)dydk>BV{2uZ^8ebY4frVN9l#ozlvh6K|YqgY`Z{ojK)5!aLrH~^-=J6 z^!%~M=<3A}ye4gce%Vxi#exIJ=+%>-B;rY^4+ry(jG|+7O+ByGbALW4A{n{4o5aWI z^6HfywXKiz?|Q#EGT}I#yv|cnr#JXF+8k^=bet{?|7qQ{SI|#qZ=Wt_pF{t;biBgO zduK%?d)RW1;2hec`sZyM4t|FGxt{McHHY?D=yg-(f%5+FgMQZW9Qt|1{6{V^IU=(D z)Gx=KPtc2J4z8lp-=Tf~=v$%N3EIdhsz=ApA4TMIo2;HQPtez0>p$+h3gziG=x#>t z2^!l)*5$(U7YOfp@lB2Pm$^=N zAphBdF?S`WX~0I&rcAdFBJybTiDfHK)0TaotnVF)@hSOACE0`1)UUVW^%+i(#|P>` z?VA5d9hd(=!momVUPi8y;!hg->5^dUCG-#DE%k%n{-na|*Zy3jLi={!I^b0PpY(Bs znPct`J`Vl8Z`E$kGjx)ly3Vi-=&zocLozjIXxr$*t&0w#{JZ)t-?ZfntvB!2Cx?}& zzn2Tf?R<5Hsy#<;xlse*O}!nZb@S-bsqenoKN`~!AK@SGBJya}Q{!VB9d-02@#X$( zGAEDDE-Y6^JhXx@Y2aE#ayE~KKB!{1Y82el1|7@%<}5AubH>sR-&gV_>TBJ;i9JiZ z{qRz_CEmf8H1~NmZQ)tkW_!BsmLJNK`m9d1>u0ItXfi16%; zLih9Z?yZ_zM)ZSxX5Xm(Ch0uY82eRz>RHj3tVzqLy6HTf(rIh+g%i;KzSGYRd~u!* z`}oa#(P&p+(k??ZtNjJKp#0^-`=v;)_Ju0>BQH=>V@b{F*AXs$uSnQ;fv#;jXTYn@ z&c5Vs)lDyoF3_Z2g7YD>F@EQD2wEI?kse*$#eOh+$il)N_p`dZ{?*!}D>U0kt6t%dE-zGog6e&6m2O^7_5rVg#*OJe^z zqfNa+JAKz+=S(-~Bm1@U#_qd910R)Ft}B9kpV_^2b-Anb`JA6ymtS=qdoWn%5{5uKPUh zEaYwV+&LkGuF-u<*EZXJx3Vu;SfN6jmDgxzpZV3ZgE8Nvm0~(ShP%b7igP@`Pf6d% z+FYlf0y->d6oUNfy;8U9uhVIfi=O_Rj`saMDb9W0b^7CtqOJMW(7${p`)1kQpxwxqd^!9ItjWy%5L}bQy4JDUv(7KWkyN

D5iTiejFRsNbB(Jk7xwM+P$kvBvnadCzHlW$Rn@IQ78T8sQnIo9KP?k)Q4 z)U}ZfrlI^>n>2K*ahuLmjUP4r3i{i858=W=x9Qr6iyq34Lf*5Qf9|;EHccBO&#qIY z9QsGUq4wt6wBoJkvk8@<|Ki6w7S%OUr$yV{uKkYit{czJ6&q=fpdWVs=uy{~Ec;aG zD>KqAJu?Rd4TL_rck&>aVWc6du+#qiFn_4mc;)qtM*53KfIzdOwlDGMW$O9RNb?J~ zjy~}GIp((!Sxda`(C2e^_Rg!1@$nn!o$jOW(A|44C0uBW@+5v*mUQe6RrJ_4wVX$F zU$S~@@|&u6>FfqBC-D4~FHsIKz8G|uHpq25xx7hte zkGFTJe&W8i_ir-yls3D9?oo$o!acQ{)%PVkzZb5UdygtU2ez#^2>A(CI99uJkM>>T zTYczxxTEHebM?AU`#E)e{QLpxzrJwL&*SgY1~XQ!PyU4VtFtt7+nM`xp8tbRGtq{dLs;U(N@!4?mz2kDqVas6F!gUcE~G z?L#{LTH4Mdk3g^Zqvs*TLptJU$3pfA5G^b)jn>b{evT0Hy@1r2!dkjV!ydd|3fELzI9MPuf6Jzc}$)1 zdffWBwxKWiw0&EPE01aN*l?$FqrrdtJ6TlwC$#1JvHioVzB4pV8q%-lbM;g89_m1=sF0E}-W&FIv&-KKj$d&EITDFQ7@aeK$1> zM0(YB2K+?KR4~mrWXVbBBmL2Hhf~b-McU=+bJn2zL8&|AADQV#_f{jTsvysz#&vtP zDWus&2Zw(g?%_)w&r%Om7SdZDA67qzX@>Q2N6Cxd;1*1+5#fOTb%~DY@U)PA%AB%v zuh~qS%n9n>8RpST*S^_aV5SSFv^nVs)31Za*fr10w3TpUt9mf!*IadFPQIB&&Mq{R zgSq2}kxhp`HPdBBg~x6^G1E39PsewInKLc^!`8=U`b>X#PFE-^Xz01R#^J~{& z(N!}&nHb$ed&NwTbSvn&?y{NA9rYmM`Xw{9&#EeRg*iIyyOv!qn(5({!z#-!n5p?p z$m6-^&9t@q&b@ojndu7E+$nd@nyL8H#t~_XX1G64PEB-Xo z@i~JFr<^v^8hZmKW#pQvVvNjn_bD@-I&JaHGbhdT?elHq{s}YvN%DQtn;bJu`1bxO zN0@ydENN8ZxS4k9JW1K;n3*P4lW%Bw)J!uS>)r7_Vx}JcYL_mD&GhAjw(fq1%(PmA zc3s2=QO`wGEY3F5#^DoE_8l-`Y)V_B-&!Hu_KS_f z*FYYM=xvu(W;$Y8xf8!E$Nkxfk;B}Undzep?}MwBfUkDnsd|ge)HVL`_p=t5Y3#CX zFJI0#Q|G2tD@D(Rd;OEM>*tv1?Vp>9-_3%*{j%PEGt9KzZ>;9F9cD(rM*4YHeZv$_S6>(;o`|USx6@$!l>tqt*(cesG zwu}A77;C0$?j#QUAsXeSV;lHHnd#jt*JIB{KrRIP?GnSyR4<5b_#zncn00);KEO=d zZWApl^fS|??x#M+cSZX?o0OH?(M+ceQr~awW2Tj(X3M8~p*~zMhFxl9ri;3?pIG0+ zOvA6vt2(eL+PBr8FMn)grcW|=HO;DTrrj$yTy?LunYuY2Nh+#grUtbnty(oRZBc#Q ziF%dIbjfzwuYrr1o_=}Et)?UT6+Q3nWM`%YrwSznp9<)^&S_hBy)B@fYgFpo%UnR4 z&VAG?=V1X2*|hRrom&O;<8JZk@QVd>SS{i6nA`%owS0Lm(cuExu$}X;qTL1bpl1EJ zUo#76%~rc3YOgJz;zvWasFxPdj>~J$UOumYE^EGZ_r~c3bY5&|^#$V#=%XOf$o~2Q z8aGg!{cczR^%xX-YUbXh^f#40DB(I(Gji<9?1qYYb!TwIy?jJ^ztDcZd98U3@*`+Ez1enwkV zS{~VZ`ZJn4>Nkfcqo2_#_mvlVtDaGpo~|p*ipFY=x_+0rRp9*r%wVID{X8YX${emOOpoW(=oR*OgkbFufx0@d%Nb-4lR!6|JWj*?jLipeP@hQC(Ezyve_x0 zhE4r$#b2+VQf)`qYSr#PrO!`=w|;;2DV^vx>Zi?FPwBJH1Aj2Bd`f?~FOMJPfBOjyeecs;yZs3bKbN*4XT}rS)b6*N zzC)f+vqRv(KifT_IX%|R*E>9+my!?e@x1()e)~D{*sHaV>5T6msSc$+rr&+HuKC=M z$8^F=Wu3v*F`oavy0P$%i9TPj{`lDqCOY-n*bd566RlsrsEbK#qNHR0PJR_k)NRUs zVPf7RDr@ec?DO*@Ix0w9xx#=)bpG0R!-h9_L@z~=Zc~j9X~^#NIilqcX>QNv-KNJq zq$96hkPT_@km8Z-^P8_dpuaR;5}oqX13EIb?!q6tKcGqX^W2IY9#EG_(QVf6y-)YA zOdB~yeV<1C+CeC7be~r3pOzAS_8#5wVa4y?jK4?cd^*2nZHs%>C%l}(_*vl&}p6OhhH6hhyG#OzDeJg-~G$JUvALfW{)|a^V4;%c4jg2qRJyl|q_2!(TQNy1O zt9LGsI)AQsw%?x9w5mo>xT@6&x~Q76@ulg9X+poP(_b&%f&I?v-p$thD5}*uDCR?> zzbx+M`*?SSY(9LsC7gfG27BiQe*c{N#c`8GMX*^-l8rBj+Jtk~-rRLG%bUBlX6;PA zz9aeinykkwW_s>1{Y)+X^!V_4_+LgX^t4C*k?#3Nexw$9>Xw4L8tcBNix;tL7G-JU z;?qOIe7f9o>986p?-meq=l7ojJg15YxxBw!ytAu?u1r1)JM#Wt`z@qxlap(D?$&0t z+UBuuO-=5(Bx9}Se)}x^=m$mha*lt@%c{6WA=>pWhozrgJ2}2Myno}R^PW^(ljXSV zqNn5hb1Xb#_BnlV_?e%QrY;||H7jBL#x-}xa?d##pS2oV9G+I~y;k1kKvvf`j-3Xz zyv5=Rr&k&*vZk*~J33(K+@o1J@{OIl9>2qd-)uUwIQ-L!-=jZQI-PZ?eubtlBkptI z!<^*B;qA;H7o|-+Nl$kjpU9ruVf=k=&a|X&qeoU)ERJFU!*YSzl@Y zEQ|cmugFi9MgHhl3{Sq{V&VXf9Y5HZ&pc5 zf5}!@Ha??g{of*Ag=N!c;jI4`hnLAe3upbmIJ`{xIsWMX#o=Y@kA<`TzYiv^!m{c6c1=J?yRA1$N*xp15Ir)9!foW2mKpz8I2->g?Ndn0jGrvrw*QnJe_6O~|0_FwbNp@k zuXWzR{Baqxs&dx^i~nj`C!-wxTV&N#9{g5$FgDBQcjSWPkJS-YuLQ{Hn%?F8<<283CjM=*%^=?ph6F%|}%k98a>%;LlU zKELAW%KrW{U-@Ug!p(R7nXi0#zFT&@r079HoB8rT^A&!*Vx4a>y=bd%%g)z0eQKkx z%hn$Y|7X7P&wRx)o*Vy}uaw>|VEyC&SMwDiUa`h2mUv#6fA?RA<2WRE*qt2$BMekZ zv|3@9LWWld669&ZAelBHMWL|-6Cr!~*+Rs<@`p&aekJ61IYH=7gzS~SGECyQN92WC z_U>W=9@*y}m>#N^q)3~SIO};HBk1EAX_lC9`oBni1^$k$*{v z+1!7^Pnw1&gpno78#9OhyJZj&cRYsQ3MQfuJi1R1ov;C`DbU)l1pzr}$?OdHX$eOQE{qVo z=)tKBI0=){Mu_VR;5B^Y1N^|yBFmf{GrA!|@S=!PF;pd!!j4ypP!$PymvhJvjgB1& zF=PndAY#SkXcHw`9q$-2guU)$wc~x4Vj7KvdGoyS9DaflJecB=x-&m^Imh8$Lg3X9 zd~^i30ptNkfPFwFunIVglgsn_BMjIFECCG6MA&>_p%>0xfjJA9222JFz!*RSC;>Sj z1>%5#KwlsRhyqsCsVInm83qIaVn76R0(<~3pcUW_xB)_-9#9LY4hR5Oz!|Uy2=H-m zMZr5DA20$}fLvf3unw3H=zti&2dECb8iX842fT?gWy%x> zoZseUh{AU$or!z%PEmaf#EHF(5*0NKNebN!(+r43@H&cIE_Zfz8mZBwYBYa)4+SLu zS6@O>_Rk&f1Mz=lL%!q=!y7{X>Tfdn`(J7L38dt&9Qn`wY&lFMC4YA0Kl`)B7ye(} z_I%<0-m3EdX$dGdq4=-de|TA2keL(9mHgS2`_KNY@&8`>|F=H>t_QG`s^ia^B6F44 z1>`?0u!XEF{eM>45-N5ON$J0Gc*_qPy%Q!*nmlFd_tU1&m^o|q4|CFgoI7v+Pd_hM zxM=Y&OP2n+EMxhKm8({-S-WoiZ@+K&W8zKkaU zFUAK@Azm8e!Q!*Hq?<%VqS-kOQtn785|X0eY%y|uiq_)SM<&H7KfEIbuURu&q*}#! zMB@ZJ785*JT%231C!i;wV>ATeEZdgL`VMSi5$|Cf&znL^c(GxChC!GCblEVoVRDz@ zNIy=Z-R`gI)XRJ zxlG~Z^tS8_3ki$BOcn{3QazM`3j+<&s8dPXCOy0RcPB8zyN7^=nL(Tx&H1C8C@xBk z!lMpQ6|6?kei9X`8MTc{N8O_d&=P16v(=78Hu3m#1meY|WJ*iD#KtN-g2MlW2$YClRx}@YF#@Y+=l=cFz zWP3q6AXLEwi~vEH5D)`#fB_Knfg8{OUTXMj>;+N7?FHE|Gnm=eUNHH||4ceJzLHKM z@)4C{FUSPMT9^RQ{rmDnfySUm*+!uJFtcH@^6<}nouI@`$F_0Ug>!};Q?I9AF6YMV@=)0p;&G;IM1=r-9cc0squ0aq{C!( zcFrSC9SY=+eu*5K5`q(8a5g(*xsUNcIICm(XXqz29g5GMj)0py1_jRXdXIYlU@v%uFb&++zLskVk3(18iiCw;+Y>ZrNuTyish`a9^8H#_#!hsG(Gh)HcsO39376cfz%;? zDY`TioK+Yq!*a4eA*d+qtnhneIGaU^y~ko!92d(WgI^BA`A{Y2ugIh&jJv7hgD^x8 zf@46CGhJFFHeO^YxapBZh(e3w{iIxuSS6L&_#q@!ouZWTq-?BJB&P8#!irOtm}?t# zGB?gz>?ooZ^*s`^1y(-vH*EK?I>vb|oE_DG^tA*+z=f3dW#=tw`mijpIO&i%1IDE6 z3eZ951Mm`7t@mXEGDJp4^3VPSfe( z*Y*582XgFjkSU)Ie~KygPFo0Z?zK7z2P?&EaRL)WhCje`gyrC1cK&E9_PGYj!KU0v zPBwHb+*82gG5`-bL6@K zauThxm2}1fv|L05cYt8Dt`~PuIEx&uLwB`Aw8TR;xntB3#ᑨa^ufXP|}i&u(| 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 Date: Mon, 18 Dec 2017 01:27:37 -0500 Subject: [PATCH 152/152] [GL] Fix skinned NiMesh rendering from 11f2980 POSITION_BP semantic was being ignored for the check that the mesh had vertices. This was causing skinned meshes using POSITION_BP to no longer render. --- src/data/niftypes.h | 20 ++++++++++++-------- src/gl/glmesh.cpp | 2 +- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/data/niftypes.h b/src/data/niftypes.h index 6f13cb869..eab32f167 100644 --- a/src/data/niftypes.h +++ b/src/data/niftypes.h @@ -2052,15 +2052,19 @@ typedef enum typedef enum { HAS_NONE = 0, - HAS_POSITION = 1 << E_POSITION, - HAS_NORMAL = 1 << E_NORMAL, - HAS_BINORMAL = 1 << E_BINORMAL, - HAS_TANGENT = 1 << E_TANGENT, - HAS_TEXCOORD = 1 << E_TEXCOORD, - HAS_BLENDWEIGHT = 1 << E_BLENDWEIGHT, + HAS_POSITION = 1 << E_POSITION, + HAS_NORMAL = 1 << E_NORMAL, + HAS_BINORMAL = 1 << E_BINORMAL, + HAS_TANGENT = 1 << E_TANGENT, + HAS_TEXCOORD = 1 << E_TEXCOORD, + HAS_BLENDWEIGHT = 1 << E_BLENDWEIGHT, HAS_BLENDINDICES = 1 << E_BLENDINDICES, - HAS_COLOR = 1 << E_COLOR, - HAS_INDEX = 1 << E_INDEX + HAS_COLOR = 1 << E_COLOR, + HAS_INDEX = 1 << E_INDEX, + HAS_POSITION_BP = 1 << E_POSITION_BP, + HAS_NORMAL_BP = 1 << E_NORMAL_BP, + HAS_BINORMAL_BP = 1 << E_BINORMAL_BP, + HAS_TANGENT_BP = 1 << E_TANGENT_BP, } SemanticFlags; #define SEM(string) {#string, E_##string}, diff --git a/src/gl/glmesh.cpp b/src/gl/glmesh.cpp index 9e1c51b81..63a44069a 100644 --- a/src/gl/glmesh.cpp +++ b/src/gl/glmesh.cpp @@ -381,7 +381,7 @@ void Mesh::transform() } // This NiMesh does not have vertices, abort - if ( !(semFlags & NiMesh::HAS_POSITION) ) + if ( !(semFlags & NiMesh::HAS_POSITION || semFlags & NiMesh::HAS_POSITION_BP) ) return; // The number of triangle indices across the submeshes for this NiMesh

NifSkope is free software available under a BSD license. The source is available via GitHub