diff --git a/documentation/devProcess/monitorResolutionTest.txt b/documentation/devProcess/monitorResolutionTest.txt new file mode 100644 index 000000000..b2edbe36c --- /dev/null +++ b/documentation/devProcess/monitorResolutionTest.txt @@ -0,0 +1,27 @@ +Find modeline with: + +gtf 1280 720 60 + + +Add mode: + +xrandr --newmode "1280x720_60.00" 74.48 1280 1336 1472 1664 720 721 724 746 -HSync +Vsync + + +Add mode to monitor: + +xrandr --addmode eDP-1 "1280x720_60.00" + + + +NOTE: this seems to cause a problem. +Disable stretching: + +xrandr --output eDP-1 --set "scaling mode" "Full aspect" + + + + +Switch to mode using monitor control panel + + diff --git a/gameSource/EditorCategoryPage.cpp b/gameSource/EditorCategoryPage.cpp index dbf052d60..7d49f355e 100644 --- a/gameSource/EditorCategoryPage.cpp +++ b/gameSource/EditorCategoryPage.cpp @@ -120,6 +120,7 @@ EditorCategoryPage::EditorCategoryPage() addKeyClassDescription( &mKeyLegend, "Pg Up/Down", "Change order" ); addKeyClassDescription( &mKeyLegend, "Ctr/Shft", "Bigger jumps" ); addKeyClassDescription( &mKeyLegend, "Bkspce", "Remv item" ); + addKeyDescription( &mKeyLegend, 'c', "Copy CSV to clipboard" ); addKeyDescription( &mKeyLegendPattern, 'd', "Duplicate item in place" ); addKeyDescription( &mKeyLegendPattern, 'D', "Duplicate item to bottom" ); @@ -703,6 +704,32 @@ void EditorCategoryPage::keyDown( unsigned char inASCII ) { updateCheckbox(); } } + + if( inASCII == 'c' && mCurrentCategory != -1 ) { + CategoryRecord *cat = getCategory( mCurrentCategory ); + + if( cat != NULL ) { + + SimpleVector workingCSV; + + + for( int i=0; i< cat->objectIDSet.size(); i++ ) { + int id = cat->objectIDSet.getElementDirect( i ); + + ObjectRecord *o = getObject( id ); + + workingCSV.appendElementString( "\"" ); + workingCSV.appendElementString( o->description ); + workingCSV.appendElementString( "\"\n" ); + } + + char *csvText = workingCSV.getElementString(); + + setClipboardText( csvText ); + + delete [] csvText; + } + } } diff --git a/gameSource/EditorScenePage.cpp b/gameSource/EditorScenePage.cpp index 7b62a0e18..198565430 100644 --- a/gameSource/EditorScenePage.cpp +++ b/gameSource/EditorScenePage.cpp @@ -66,6 +66,7 @@ EditorScenePage::EditorScenePage() mReplaceButton( smallFont, -500, 260, "Replace" ), mDeleteButton( smallFont, 500, 260, "Delete" ), mSaveTestMapButton( smallFont, -300, 200, "Export Test Map" ), + mLoadTestMapButton( smallFont, -500, 200, "Import Test Map" ), mNextSceneButton( smallFont, -420, 260, ">" ), mPrevSceneButton( smallFont, -580, 260, "<" ), mClearSceneButton( smallFont, 350, 260, "Clear" ), @@ -152,6 +153,16 @@ EditorScenePage::EditorScenePage() addComponent( &mSaveTestMapButton ); mSaveTestMapButton.addActionListener( this ); + addComponent( &mLoadTestMapButton ); + mLoadTestMapButton.addActionListener( this ); + + File *testSceneFile = new File( NULL, "testMapScene.txt" ); + + mLoadTestMapButton.setVisible( testSceneFile->exists() ); + + delete testSceneFile; + + addComponent( &mNextSceneButton ); mNextSceneButton.addActionListener( this ); @@ -478,6 +489,16 @@ void EditorScenePage::actionPerformed( GUIComponent *inTarget ) { checkNextPrevVisible(); } else if( inTarget == &mSaveTestMapButton ) { + // save as scene file too, for easy re-loading later + File *sceneFile = new File( NULL, "testMapScene.txt" ); + + writeSceneToFile( sceneFile ); + + delete sceneFile; + + mLoadTestMapButton.setVisible( true ); + + FILE *f = fopen( "testMap.txt", "w" ); if( f != NULL ) { @@ -521,6 +542,18 @@ void EditorScenePage::actionPerformed( GUIComponent *inTarget ) { fclose( f ); } } + else if( inTarget == &mLoadTestMapButton ) { + // just re-load our saved test scene file + File *sceneFile = new File( NULL, "testMapScene.txt" ); + + char r = tryLoadScene( sceneFile ); + + delete sceneFile; + + if( !r ) { + printf( "Failed to load scene from testMapScene.txt\n" ); + } + } else if( inTarget == &mReplaceButton ) { writeSceneToFile( mSceneID ); } @@ -2619,6 +2652,15 @@ void addCellLines( SimpleVector *inLines, void EditorScenePage::writeSceneToFile( int inIDToUse ) { File *f = getSceneFile( inIDToUse ); + + writeSceneToFile( f ); + + delete f; + } + + + +void EditorScenePage::writeSceneToFile( File *inFile ) { SimpleVector lines; @@ -2659,10 +2701,8 @@ void EditorScenePage::writeSceneToFile( int inIDToUse ) { delete [] linesArray; lines.deallocateStringElements(); - f->writeToFile( contents ); + inFile->writeToFile( contents ); delete [] contents; - - delete f; } @@ -2835,89 +2875,98 @@ char EditorScenePage::tryLoadScene( int inSceneID ) { if( f->exists() && ! f->isDirectory() ) { printf( "Trying to load scene %d\n", inSceneID ); + r = tryLoadScene( f ); + } + + delete f; + + return r; + } + + + +char EditorScenePage::tryLoadScene( File *inFile ) { + + char r = false; + + char *fileText = inFile->readFileContents(); - char *fileText = f->readFileContents(); - - if( fileText != NULL ) { + if( fileText != NULL ) { - int numLines = 0; + int numLines = 0; - char **lines = split( fileText, "\n", &numLines ); - delete [] fileText; + char **lines = split( fileText, "\n", &numLines ); + delete [] fileText; - int next = 0; + int next = 0; - int w = mSceneW; - int h = mSceneH; + int w = mSceneW; + int h = mSceneH; - sscanf( lines[next], "w=%d", &w ); - next++; - sscanf( lines[next], "h=%d", &h ); - next++; + sscanf( lines[next], "w=%d", &w ); + next++; + sscanf( lines[next], "h=%d", &h ); + next++; - if( w != mSceneW || h != mSceneH ) { - resizeGrid( h, w ); - } + if( w != mSceneW || h != mSceneH ) { + resizeGrid( h, w ); + } - if( strstr( lines[next], "origin" ) != NULL ) { - sscanf( lines[next], "origin=%d,%d", &mZeroX, &mZeroY ); - next++; - } + if( strstr( lines[next], "origin" ) != NULL ) { + sscanf( lines[next], "origin=%d,%d", &mZeroX, &mZeroY ); + next++; + } - char floorPresent = false; + char floorPresent = false; - if( strstr( lines[next], "floorPresent" ) != NULL ) { - floorPresent = true; - next++; - } + if( strstr( lines[next], "floorPresent" ) != NULL ) { + floorPresent = true; + next++; + } - clearScene(); + clearScene(); - int numRead = 0; + int numRead = 0; - int x, y; + int x, y; - numRead = sscanf( lines[next], "x=%d,y=%d", &x, &y ); - next++; + numRead = sscanf( lines[next], "x=%d,y=%d", &x, &y ); + next++; - while( numRead == 2 ) { - SceneCell *c = &( mCells[y][x] ); - SceneCell *p = &( mPersonCells[y][x] ); - SceneCell *f = &( mFloorCells[y][x] ); + while( numRead == 2 ) { + SceneCell *c = &( mCells[y][x] ); + SceneCell *p = &( mPersonCells[y][x] ); + SceneCell *f = &( mFloorCells[y][x] ); - next = scanCell( lines, next, c ); - next = scanCell( lines, next, p ); + next = scanCell( lines, next, c ); + next = scanCell( lines, next, p ); - if( floorPresent ) { - next = scanCell( lines, next, f ); - } + if( floorPresent ) { + next = scanCell( lines, next, f ); + } - numRead = 0; + numRead = 0; - if( next < numLines ) { - numRead = sscanf( lines[next], "x=%d,y=%d", &x, &y ); - next++; - } + if( next < numLines ) { + numRead = sscanf( lines[next], "x=%d,y=%d", &x, &y ); + next++; } + } - for( int i=0; idrawString( noteString, notePos, alignLeft ); diff --git a/gameSource/ExistingAccountPage.cpp b/gameSource/ExistingAccountPage.cpp index 1be518ec3..e7475aae8 100644 --- a/gameSource/ExistingAccountPage.cpp +++ b/gameSource/ExistingAccountPage.cpp @@ -1496,7 +1496,7 @@ void ExistingAccountPage::draw( doublePair inViewCenter, setStatusDirect( message, true ); delete [] message; - setStatusPositiion( true ); + setStatusPosition( true ); mRetryButton.setVisible( true ); mRedetectButton.setVisible( true ); mCancelButton.setVisible( true ); diff --git a/gameSource/GamePage.cpp b/gameSource/GamePage.cpp index e407b424b..ffcacf109 100644 --- a/gameSource/GamePage.cpp +++ b/gameSource/GamePage.cpp @@ -183,7 +183,7 @@ void GamePage::setTipPosition( char inTop ) { } -void GamePage::setStatusPositiion( char inTop ) { +void GamePage::setStatusPosition( char inTop ) { mStatusAtTopOfScreen = inTop; } diff --git a/gameSource/GamePage.h b/gameSource/GamePage.h index 3c4dc307d..26b6b0e76 100644 --- a/gameSource/GamePage.h +++ b/gameSource/GamePage.h @@ -30,7 +30,7 @@ class GamePage : public PageComponent { // overrides default status position // status messages default to bottom of screen - void setStatusPositiion( char inTop ); + void setStatusPosition( char inTop ); // override these from PageComponent to actually SHOW diff --git a/gameSource/LivingLifePage.cpp b/gameSource/LivingLifePage.cpp index 18e18c896..a7b83b990 100644 --- a/gameSource/LivingLifePage.cpp +++ b/gameSource/LivingLifePage.cpp @@ -1076,6 +1076,12 @@ void LivingLifePage::sendToServerSocket( char *inMessage ) { timeLastMessageSent = game_getCurrentTime(); printf( "Sending message to server: %s\n", inMessage ); + + if( mServerSocket == -1 ) { + printf( "Server socket already closed, skipping sending message: %s\n", + inMessage ); + return; + } replaceLastMessageSent( stringDuplicate( inMessage ) ); @@ -3283,6 +3289,7 @@ LivingLifePage::~LivingLifePage() { if( mServerSocket != -1 ) { closeSocket( mServerSocket ); + mServerSocket = -1; } for( int j=0; j<2; j++ ) { @@ -9008,12 +9015,16 @@ void LivingLifePage::draw( doublePair inViewCenter, pos = mult( pos, CELL_D ); pos = sub( pos, lastScreenViewCenter ); - int screenWidth, screenHeight; - getScreenDimensions( &screenWidth, &screenHeight ); + // pos is currently in world/pixel coordinates - pos.x += screenWidth / 2; - pos.y += screenHeight / 2; - + // do NOT adjust camera pos relative to screen corner yet + // we need to take screen scaling into acount + // and we don't want to recompute screen scaling here + // (we will do that in the photo code) + // pos.x += screenWidth / 2; + // pos.y += screenHeight / 2; + + char *ourName; if( ourLiveObject->name != NULL ) { @@ -11774,6 +11785,40 @@ static double getLongestLine( char *inMessage ) { +// tags with new or fresh if needed +// inBaseString is destroyed internally if it needs to change +// and a new string is returned. +// If it doesn't need to change, inBaseString is returned directly +static char *tagObjectStringWithQualifier( char *inBaseString, + ObjectRecord *inO ) { + if( inO->useChance == 1.0 ) { + // an object that steps through uses + // probably something that gets visibly used up + // step by step. + // actorMinUseFraction == 1.0 means "full" + char *taggedString = + autoSprintf( "%s %s", inBaseString, + translate( "fullHint" ) ); + delete [] inBaseString; + return taggedString; + } + else if( inO->useChance < 1.0 && + inO->useChance > 0 ) { + // an object that wears out somewhat randomly + // actorMinUseFraction == 1.0 means "unused new object" + char *taggedString = + autoSprintf( "%s %s", inBaseString, + translate( "freshHint" ) ); + delete [] inBaseString; + return taggedString; + } + + return inBaseString; + } + + + + char *LivingLifePage::getHintMessage( int inObjectID, int inIndex ) { if( inObjectID != mLastHintSortedSourceID ) { @@ -11799,12 +11844,25 @@ char *LivingLifePage::getHintMessage( int inObjectID, int inIndex ) { char *actorString; if( actor > 0 ) { - actorString = stringToUpperCase( getObject( actor )->description ); + ObjectRecord *actorO = getObject( actor ); + + actorString = stringToUpperCase( actorO->description ); stripDescriptionComment( actorString ); + + if( found->actorMinUseFraction == 1.0 && + actorO->numUses > 1 ) { + + actorString = tagObjectStringWithQualifier( actorString, + actorO ); + } } - else if( actor == 0 ) { + else if( actor == 0 || actor == -2 ) { + // show bare hand for default actions too actorString = stringDuplicate( translate( "bareHandHint" ) ); } + else if( actor == -1 ) { + actorString = stringDuplicate( translate( "timeHint" ) ); + } else { actorString = stringDuplicate( "" ); } @@ -11816,9 +11874,18 @@ char *LivingLifePage::getHintMessage( int inObjectID, int inIndex ) { char *targetString; if( target > 0 ) { + ObjectRecord *targetO = getObject( target ); + targetString = - stringToUpperCase( getObject( target )->description ); + stringToUpperCase( targetO->description ); stripDescriptionComment( targetString ); + + if( found->targetMinUseFraction == 1.0 && + targetO->numUses > 1 ) { + + targetString = tagObjectStringWithQualifier( targetString, + targetO ); + } } else if( target == -1 && actor > 0 ) { ObjectRecord *actorObj = getObject( actor ); @@ -12104,6 +12171,58 @@ doublePair LivingLifePage::getPlayerPos( LiveObject *inPlayer ) { } + + +void LivingLifePage::displayGlobalMessage( char *inMessage ) { + + char *upper = stringToUpperCase( inMessage ); + + char found; + + char *lines = replaceAll( upper, "**", "##", &found ); + delete [] upper; + + char *spaces = replaceAll( lines, "_", " ", &found ); + + delete [] lines; + + + mGlobalMessageShowing = true; + mGlobalMessageStartTime = game_getCurrentTime(); + + if( mLiveTutorialSheetIndex >= 0 ) { + mTutorialTargetOffset[ mLiveTutorialSheetIndex ] = + mTutorialHideOffset[ mLiveTutorialSheetIndex ]; + } + mLiveTutorialSheetIndex ++; + + if( mLiveTutorialSheetIndex >= NUM_HINT_SHEETS ) { + mLiveTutorialSheetIndex -= NUM_HINT_SHEETS; + } + mTutorialMessage[ mLiveTutorialSheetIndex ] = + stringDuplicate( spaces ); + + // other tutorial messages don't need to be destroyed + mGlobalMessagesToDestroy.push_back( + (char*)( mTutorialMessage[ mLiveTutorialSheetIndex ] ) ); + + mTutorialTargetOffset[ mLiveTutorialSheetIndex ] = + mTutorialHideOffset[ mLiveTutorialSheetIndex ]; + + mTutorialTargetOffset[ mLiveTutorialSheetIndex ].y -= 100; + + double longestLine = getLongestLine( + (char*)( mTutorialMessage[ mLiveTutorialSheetIndex ] ) ); + + mTutorialExtraOffset[ mLiveTutorialSheetIndex ].x = longestLine; + + + delete [] spaces; + } + + + + void LivingLifePage::setNewCraving( int inFoodID, int inYumBonus ) { char *foodDescription = stringToUpperCase( getObject( inFoodID )->description ); @@ -13440,53 +13559,17 @@ void LivingLifePage::step() { if( mTutorialNumber <= 0 ) { // not in tutorial // display this message - - char messageFromServer[200]; - sscanf( message, "MS\n%199s", messageFromServer ); - - char *upper = stringToUpperCase( messageFromServer ); - - char found; - - char *lines = replaceAll( upper, "**", "##", &found ); - delete [] upper; - - char *spaces = replaceAll( lines, "_", " ", &found ); - - delete [] lines; - - mGlobalMessageShowing = true; - mGlobalMessageStartTime = game_getCurrentTime(); + int numLines; + char **lines = split( message, "\n", &numLines ); - if( mLiveTutorialSheetIndex >= 0 ) { - mTutorialTargetOffset[ mLiveTutorialSheetIndex ] = - mTutorialHideOffset[ mLiveTutorialSheetIndex ]; + if( numLines > 1 ) { + displayGlobalMessage( lines[1] ); } - mLiveTutorialSheetIndex ++; - - if( mLiveTutorialSheetIndex >= NUM_HINT_SHEETS ) { - mLiveTutorialSheetIndex -= NUM_HINT_SHEETS; + for( int i=0; i= 4 ) { + // we scanned past the 4th skipped token + // now tokenize to extract it + // do this in place to avoid allocating a bunch + // of strings that we don't need + + lineCopy = stringDuplicate( lines[i] ); + + SimpleVector *tokenPointers = + tokenizeStringInPlace( lineCopy ); + + if( tokenPointers->size() >= 4 ) { + idBuffer = tokenPointers->getElementDirect( 3 ); + } + + // we can safely delete vector, since it only + // contains pointers into lineCopy + delete tokenPointers; + + // we also don't need to worry about deleting idBuffer + // since it's a pointer into lineCopy + + // lineCopy is now mangled and full of \0, but that's okay + // because it's a copy + + // and we just scanned one more token + numRead ++; + } + + if( numRead == 5 || numRead == 8) { applyReceiveOffset( &x, &y ); @@ -14819,6 +14939,9 @@ void LivingLifePage::step() { lines[i] ) ); delete [] lines[i]; + if( lineCopy != NULL ) { + delete [] lineCopy; + } continue; } } @@ -15201,8 +15324,10 @@ void LivingLifePage::step() { // floor changed ObjectRecord *obj = getObject( floorID ); - if( obj->creationSound.numSubSounds > 0 ) { - + if( obj->creationSound.numSubSounds > 0 && + shouldCreationSoundPlay( oldFloor, + floorID ) ) { + playSound( obj->creationSound, getVectorFromCamera( x, y ) ); } @@ -15681,6 +15806,9 @@ void LivingLifePage::step() { } delete [] lines[i]; + if( lineCopy != NULL ) { + delete [] lineCopy; + } } delete [] lines; @@ -15798,12 +15926,12 @@ void LivingLifePage::step() { int forced = 0; int done_moving = 0; - char *holdingIDBuffer = new char[500]; + char *holdingIDBuffer = NULL; int heldOriginValid, heldOriginX, heldOriginY, heldTransitionSourceID; - char *clothingBuffer = new char[500]; + char *clothingBuffer = NULL; int justAte = 0; int justAteID = 0; @@ -15818,22 +15946,27 @@ void LivingLifePage::step() { int responsiblePlayerID = -1; int heldYum = 0; - + int heldLearned = 1; + + // skip strings of unknown length in middle + // 7th string and 20th string + // %*s skips them + // numRead won't include these skipped strings in the count int numRead = sscanf( lines[i], "%d %d " "%d " "%d " "%d %d " - "%499s %d %d %d %d %f %d %d %d %d " - "%lf %lf %lf %499s %d %d %d " - "%d", + "%*s %d %d %d %d %f %d %d %d %d " + "%lf %lf %lf %*s %d %d %d " + "%d %d", &( o.id ), &( o.displayID ), &facingOverride, &actionAttempt, &actionTargetX, &actionTargetY, - holdingIDBuffer, + // skip 7th string &heldOriginValid, &heldOriginX, &heldOriginY, @@ -15846,14 +15979,50 @@ void LivingLifePage::step() { &( o.age ), &invAgeRate, &( o.lastSpeed ), - clothingBuffer, + // skip 20th string &justAte, &justAteID, &responsiblePlayerID, &heldYum); + char *lineCopy = NULL; + if( numRead >= 21 ) { + // scanned all but skipped strings + + // now tokenize to extract them + // do this in place to avoid allocating a bunch + // of strings that we don't need + + lineCopy = stringDuplicate( lines[i] ); + + SimpleVector *tokenPointers = + tokenizeStringInPlace( lineCopy ); + + if( tokenPointers->size() >= 20 ) { + // 7th string + holdingIDBuffer = tokenPointers->getElementDirect( 6 ); + // 20th string + clothingBuffer = tokenPointers->getElementDirect( 19 ); + } + + // we can safely delete vector, since it only + // contains pointers into lineCopy + delete tokenPointers; + + // we also don't need to worry about deleting either + // id buffer + // since they're pointers into lineCopy + + // lineCopy is now mangled and full of \0, but that's okay + // because it's a copy + + // and we just scanned two more tokens + numRead += 2; + } + // heldYum is 24th value, optional + // heldLearned is 25th value, optional if( numRead >= 23 ) { applyReceiveOffset( &actionTargetX, &actionTargetY ); @@ -17771,10 +17940,12 @@ void LivingLifePage::step() { } } - delete [] holdingIDBuffer; - delete [] clothingBuffer; delete [] lines[i]; + + if( lineCopy != NULL ) { + delete [] lineCopy; + } } for( int i=0; ihitOurPlacement && - getObjectHeight( oID ) > .75 * CELL_D ) { + getObjectHeight( oID ) > .75 * CELL_D && + !obj->noClickThrough // object prevents click-through + ) { if( p->closestCellY > y ) { p->hitOurPlacementBehind = true; @@ -23566,7 +23741,11 @@ void LivingLifePage::pointerDown( float inX, float inY ) { } } - if( !foundAlt && floorDestID > 0 ) { + if( !foundAlt && floorDestID > 0 && + // require shift right click to interact + // with floor + isShiftKeyDown() + ) { // check if use on floor exists TransRecord *r = getTrans( ourLiveObject->holdingID, diff --git a/gameSource/LivingLifePage.h b/gameSource/LivingLifePage.h index c8216f3b5..71b6fdd53 100644 --- a/gameSource/LivingLifePage.h +++ b/gameSource/LivingLifePage.h @@ -1080,6 +1080,10 @@ class LivingLifePage : public GamePage, public ActionListener { void drawHomeSlip( doublePair inSlipPos, int inIndex = 0 ); + + + void displayGlobalMessage( char *inMessage ); + }; diff --git a/gameSource/OneLife.exe b/gameSource/OneLife.exe new file mode 100644 index 000000000..9272b90dc Binary files /dev/null and b/gameSource/OneLife.exe differ diff --git a/gameSource/SettingsPage.cpp b/gameSource/SettingsPage.cpp index 7ad4317b8..428e00ec7 100644 --- a/gameSource/SettingsPage.cpp +++ b/gameSource/SettingsPage.cpp @@ -25,6 +25,8 @@ extern Font * mainFont; extern float musicLoudness; +extern int targetFramesPerSecond; + extern bool showingInGameSettings; // defined in LivingLifePage.cpp @@ -82,8 +84,13 @@ SettingsPage::SettingsPage() // Screen mRedetectButton( mainFont, 153, 249, translate( "redetectButton" ) ), + mVsyncBox( 0, 208, 4 ), mFullscreenBox( 0, 128, 4 ), mBorderlessBox( 0, 168, 4 ), + mTargetFrameRateField( mainFont, -16, 232, 3, false, + "", // no label + "0123456789", + NULL ), mTrippingEffectDisabledBox( 0, 168, 4 ), // Sound @@ -137,12 +144,23 @@ SettingsPage::SettingsPage() mTrippingEffectDisabledBox.addActionListener( this ); addComponent( &mBorderlessBox ); mBorderlessBox.addActionListener( this ); + addComponent( &mTargetFrameRateField ); + mTargetFrameRateField.addActionListener( this ); + mTargetFrameRateField.setFireOnAnyTextChange( true ); + addComponent( &mVsyncBox ); + mVsyncBox.addActionListener( this ); addComponent( &mFullscreenBox ); mFullscreenBox.addActionListener( this ); setButtonStyle( &mRedetectButton ); addComponent( &mRedetectButton ); mRedetectButton.addActionListener( this ); + mTargetFrameRateField.setInt( targetFramesPerSecond ); + + if( getCountingOnVsync() ) { + mTargetFrameRateField.setVisible( false ); + } + // Control addComponent( &mCursorScaleSlider ); mCursorScaleSlider.addActionListener( this ); @@ -285,6 +303,9 @@ SettingsPage::SettingsPage() mMusicStartTime = 0; + + mVsyncBox.setToggled( getCountingOnVsync() ); + mFullscreenBox.setToggled( mOldFullscreenSetting ); mBorderlessBox.setVisible( mOldFullscreenSetting ); @@ -371,6 +392,34 @@ SettingsPage::~SettingsPage() { +void SettingsPage::checkRestartButtonVisibility() { + int newVsyncSetting = + SettingsManager::getIntSetting( "countingOnVsync", -1 ); + + int newFullscreenSetting = + SettingsManager::getIntSetting( "fullscreen", 1 ); + + int newBorderlessSetting = + SettingsManager::getIntSetting( "borderless", 0 ); + + if( getCountingOnVsync() != newVsyncSetting + || + mOldFullscreenSetting != newFullscreenSetting + || + mOldBorderlessSetting != newBorderlessSetting + || + ( mTargetFrameRateField.isVisible() && + mTargetFrameRateField.getInt() != targetFramesPerSecond ) ) { + + mRestartButton.setVisible( true ); + } + else { + mRestartButton.setVisible( false ); + } + } + + + void SettingsPage::actionPerformed( GUIComponent *inTarget ) { if( inTarget == &mBackButton ) { @@ -396,15 +445,62 @@ void SettingsPage::actionPerformed( GUIComponent *inTarget ) { setSignal( "editAccount" ); setMusicLoudness( 0 ); } + else if( inTarget == &mTargetFrameRateField ) { + + int newValue = mTargetFrameRateField.getInt(); + + if( newValue > 0 ) { + + // if we're manually manipulating target frame rate + // any tweaked halfFrameRate setting should be cleared + SettingsManager::setSetting( "halfFrameRate", 0 ); + + SettingsManager::setSetting( "targetFrameRate", newValue ); + + // checkRestartButtonVisibility(); + } + } + else if( inTarget == &mVsyncBox ) { + int newSetting = mVsyncBox.getToggled(); + + SettingsManager::setSetting( "countingOnVsync", newSetting ); + + // checkRestartButtonVisibility(); + + if( ! newSetting ) { + mTargetFrameRateField.setVisible( true ); + mTargetFrameRateField.setInt( targetFramesPerSecond ); + } + else { + mTargetFrameRateField.setVisible( false ); + + // if we're manually manipulating target frame rate + // any tweaked halfFrameRate setting should be cleared + SettingsManager::setSetting( "halfFrameRate", 0 ); + SettingsManager::setSetting( "targetFramesPerSecond", + targetFramesPerSecond ); + } + } else if( inTarget == &mFullscreenBox ) { int newSetting = mFullscreenBox.getToggled(); SettingsManager::setSetting( "fullscreen", newSetting ); + + if( ! newSetting ) { + // can't be borderless if not fullscreen + SettingsManager::setSetting( "borderless", 0 ); + mBorderlessBox.setToggled( false ); + } + + + // checkRestartButtonVisibility(); } else if( inTarget == &mBorderlessBox ) { int newSetting = mBorderlessBox.getToggled(); SettingsManager::setSetting( "borderless", newSetting ); + + // checkRestartButtonVisibility(); } else if( inTarget == &mTrippingEffectDisabledBox ) { int newSetting = mTrippingEffectDisabledBox.getToggled(); @@ -443,12 +539,60 @@ void SettingsPage::actionPerformed( GUIComponent *inTarget ) { SettingsManager::setSetting( "centerCamera", newSetting ); } - else if( inTarget == &mRestartButton || - inTarget == &mRedetectButton ) { - // always re-measure frame rate after relaunch + else if( inTarget == &mRedetectButton ) { + // redetect means start from scratch, detect vsync, etc. SettingsManager::setSetting( "targetFrameRate", -1 ); SettingsManager::setSetting( "countingOnVsync", -1 ); + char relaunched = relaunchGame(); + + if( !relaunched ) { + printf( "Relaunch failed\n" ); + setSignal( "relaunchFailed" ); + } + else { + printf( "Relaunched... but did not exit?\n" ); + setSignal( "relaunchFailed" ); + } + } + else if( inTarget == &mRestartButton ) { + + int newVsyncSetting = + SettingsManager::getIntSetting( "countingOnVsync", -1 ); + + int newFullscreenSetting = + SettingsManager::getIntSetting( "fullscreen", 1 ); + + int newBorderlessSetting = + SettingsManager::getIntSetting( "borderless", 0 ); + + + if( ( newVsyncSetting == 1 && ! getCountingOnVsync() ) + || + ( newVsyncSetting == 1 && + newFullscreenSetting != mOldFullscreenSetting ) + || + ( newVsyncSetting == 1 && + newBorderlessSetting != mOldBorderlessSetting ) ) { + + // re-measure frame rate after relaunch + // but only if counting on vsync AND if one of these things changed + + // 1. We weren't counting on vsync before + // 2. We've toggled fullscreen mode + // 3. We've toggled borderless mode + + // if we're not counting on vsync, we shouldn't re-measure + // because measuring is only sensible if we're trying to detect + // our vsync-enforced frame rate + SettingsManager::setSetting( "targetFrameRate", -1 ); + } + + + // now that end-user can toggle vsync here, never clear + // the vsync setting when we re-launch + + char relaunched = relaunchGame(); if( !relaunched ) { @@ -653,7 +797,6 @@ void SettingsPage::actionPerformed( GUIComponent *inTarget ) { } -extern int targetFramesPerSecond; void SettingsPage::draw( doublePair inViewCenter, @@ -661,7 +804,15 @@ void SettingsPage::draw( doublePair inViewCenter, setDrawColor( 1, 1, 1, 1 ); if( mFullscreenBox.isVisible() ) { - doublePair pos = mFullscreenBox.getPosition(); + + doublePair pos = mVsyncBox.getPosition(); + + pos.x -= 24; + pos.y -= 2; + + mainFont->drawString( translate( "vsyncOn" ), pos, alignRight ); + + pos = mFullscreenBox.getPosition(); pos.x -= 30; pos.y -= 2; @@ -694,22 +845,18 @@ void SettingsPage::draw( doublePair inViewCenter, pos.y += 52; pos.x -= 16; - if( getCountingOnVsync() ) { - mainFont->drawString( translate( "vsyncYes" ), pos, alignLeft ); - } - else { - mainFont->drawString( translate( "vsyncNo" ), pos, alignLeft ); - } - pos.y += 52; + + if( ! mTargetFrameRateField.isVisible() ) { char *fpsString = autoSprintf( "%d", targetFramesPerSecond ); mainFont->drawString( fpsString, pos, alignLeft ); delete [] fpsString; + } + - - pos.y += 52; + pos.y += 52; char *currentFPSString = autoSprintf( "%.2f", getRecentFrameRate() ); @@ -721,7 +868,7 @@ void SettingsPage::draw( doublePair inViewCenter, pos.x -= 30; pos.y += 52; - mainFont->drawString( translate( "vsyncOn" ), pos, alignRight ); + pos.y += 52; mainFont->drawString( translate( "targetFPS" ), pos, alignRight ); pos.y += 52; @@ -877,6 +1024,9 @@ void SettingsPage::step() { void SettingsPage::makeActive( char inFresh ) { + + checkRestartRequired(); + if( inFresh ) { int mode = getCursorMode(); @@ -975,6 +1125,8 @@ void SettingsPage::updatePage() { mMusicLoudnessSlider.setPosition( 0, lineSpacing / 2 ); mSoundEffectsLoudnessSlider.setPosition( 0, - lineSpacing / 2 ); + mTargetFrameRateField.setPosition( 5, lineSpacing ); + mVsyncBox.setPosition( 0, 0 ); mFullscreenBox.setPosition( 0, -lineSpacing ); mBorderlessBox.setPosition( 0, -lineSpacing * 2 ); mTrippingEffectDisabledBox.setPosition( 0, -lineSpacing * 3 ); @@ -1006,6 +1158,8 @@ void SettingsPage::updatePage() { mCursorScaleSlider.setVisible( mode > 0 && mCursorModeSet->isVisible() ); + mTargetFrameRateField.setVisible( mPage == 2 && !mVsyncBox.getToggled() ); + mVsyncBox.setVisible( mPage == 2 ); mFullscreenBox.setVisible( mPage == 2 ); mBorderlessBox.setVisible( mPage == 2 && mFullscreenBox.getToggled() ); mTrippingEffectDisabledBox.setVisible( mPage == 2 ); @@ -1046,7 +1200,9 @@ void SettingsPage::updatePage() { void SettingsPage::checkRestartRequired() { if( mOldFullscreenSetting != mFullscreenBox.getToggled() || - mOldBorderlessSetting != mBorderlessBox.getToggled() + mOldBorderlessSetting != mBorderlessBox.getToggled() || + getCountingOnVsync() != mVsyncBox.getToggled() || + ( mTargetFrameRateField.isVisible() && mTargetFrameRateField.getInt() != targetFramesPerSecond ) ) { setStatusDirect( "RESTART REQUIRED##FOR NEW SETTINGS TO TAKE EFFECT", true ); // Do not show RESTART button when setting page is accessed mid-game diff --git a/gameSource/SettingsPage.h b/gameSource/SettingsPage.h index 0735e9c4c..9cc9a6e77 100644 --- a/gameSource/SettingsPage.h +++ b/gameSource/SettingsPage.h @@ -95,8 +95,10 @@ class SettingsPage : public GamePage, public ActionListener { // Screen TextButton mRedetectButton; + CheckboxButton mVsyncBox; CheckboxButton mFullscreenBox; CheckboxButton mBorderlessBox; + TextField mTargetFrameRateField; CheckboxButton mTrippingEffectDisabledBox; // Sound @@ -113,4 +115,8 @@ class SettingsPage : public GamePage, public ActionListener { #endif // USE_DISCORD CheckboxButton mEnableAdvancedShowUseOnObjectHoverKeybind; + + + void checkRestartButtonVisibility(); + }; diff --git a/gameSource/animationBank.cpp b/gameSource/animationBank.cpp index 1695d0a56..bf9ad2b3f 100644 --- a/gameSource/animationBank.cpp +++ b/gameSource/animationBank.cpp @@ -49,9 +49,9 @@ static int maxID; -static const char *animTypeNames[9] = { "ground", "held", "moving", "ground2", - "eating", "doing", "endAnimType", - "extra", "extraB" }; +static const char *animTypeNames[11] = { "ground", "held", "moving", "ground2", + "eating", "doing", "biking", "sitting", + "endAnimType", "extra", "extraB" }; const char *typeToName( AnimType inAnimType ) { diff --git a/gameSource/game.cpp b/gameSource/game.cpp index 9fac13585..6fb84ebaf 100644 --- a/gameSource/game.cpp +++ b/gameSource/game.cpp @@ -2124,7 +2124,7 @@ void drawFrame( char inUpdate ) { loginEditOverride = true; existingAccountPage->setStatus( "editAccountWarning", false ); - existingAccountPage->setStatusPositiion( false ); + existingAccountPage->setStatusPosition( false ); currentGamePage = existingAccountPage; currentGamePage->base_makeActive( true ); @@ -2339,7 +2339,7 @@ void drawFrame( char inUpdate ) { existingAccountPage->setStatus( "loginFailed", true ); - existingAccountPage->setStatusPositiion( false ); + existingAccountPage->setStatusPosition( false ); currentGamePage->base_makeActive( true ); } @@ -2354,7 +2354,7 @@ void drawFrame( char inUpdate ) { existingAccountPage->setStatusDirect( "TARGET FAMILY NOT FOUND##OR HAS NO FERTILES", true ); - existingAccountPage->setStatusPositiion( false ); + existingAccountPage->setStatusPosition( false ); currentGamePage->base_makeActive( true ); } @@ -2369,7 +2369,7 @@ void drawFrame( char inUpdate ) { existingAccountPage->setStatus( "noLifeTokens", true ); - existingAccountPage->setStatusPositiion( false ); + existingAccountPage->setStatusPosition( false ); currentGamePage->base_makeActive( true ); } @@ -2384,7 +2384,7 @@ void drawFrame( char inUpdate ) { existingAccountPage->setStatus( "connectionFailed", true ); - existingAccountPage->setStatusPositiion( false ); + existingAccountPage->setStatusPosition( false ); currentGamePage->base_makeActive( true ); } @@ -2411,7 +2411,7 @@ void drawFrame( char inUpdate ) { delete [] message; - existingAccountPage->setStatusPositiion( false ); + existingAccountPage->setStatusPosition( false ); currentGamePage->base_makeActive( true ); } @@ -2440,7 +2440,7 @@ void drawFrame( char inUpdate ) { existingAccountPage->setStatus( "serverShutdown", true ); - existingAccountPage->setStatusPositiion( false ); + existingAccountPage->setStatusPosition( false ); currentGamePage->base_makeActive( true ); } @@ -2455,7 +2455,7 @@ void drawFrame( char inUpdate ) { existingAccountPage->setStatus( "serverUpdate", true ); - existingAccountPage->setStatusPositiion( false ); + existingAccountPage->setStatusPosition( false ); currentGamePage->base_makeActive( true ); } @@ -2470,7 +2470,7 @@ void drawFrame( char inUpdate ) { existingAccountPage->setStatus( "serverFull", true ); - existingAccountPage->setStatusPositiion( false ); + existingAccountPage->setStatusPosition( false ); currentGamePage->base_makeActive( true ); } diff --git a/gameSource/languages/English.txt b/gameSource/languages/English.txt index 26d83caff..d85688424 100644 --- a/gameSource/languages/English.txt +++ b/gameSource/languages/English.txt @@ -273,6 +273,12 @@ bareGroundHint "BARE GROUND" nothingHint "NOTHING" eventuallyHint "(EVENTUALLY)" +timeHint "(TIME PASSING)" + +freshHint "(FRESH)" +fullHint "(FULL)" + + noHint "NO INFORMATION" diff --git a/gameSource/objectBank.cpp b/gameSource/objectBank.cpp index de1784197..248c01ad8 100644 --- a/gameSource/objectBank.cpp +++ b/gameSource/objectBank.cpp @@ -261,7 +261,8 @@ void setDrawColor( FloatRGB inColor ) { static char shouldFileBeCached( char *inFileName ) { if( strstr( inFileName, ".txt" ) != NULL && strstr( inFileName, "groundHeat_" ) == NULL && - strcmp( inFileName, "nextObjectNumber.txt" ) != 0 ) { + strcmp( inFileName, "nextObjectNumber.txt" ) != 0 && + strcmp( inFileName, "nextObjectNumberOffset.txt" ) != 0 ) { return true; } return false; @@ -576,6 +577,15 @@ static void setupNoHighlight( ObjectRecord *inR ) { inR->noHighlight = true; } } + + +static void setupNoClickThrough( ObjectRecord *inR ) { + inR->noClickThrough = false; + + if( strstr( inR->description, "+noClickThrough" ) != NULL ) { + inR->noClickThrough = true; + } + } @@ -795,6 +805,8 @@ float initObjectBankStep() { setupNoHighlight( r ); + setupNoClickThrough( r ); + setupMaxPickupAge( r ); setupAutoDefaultTrans( r ); @@ -2317,54 +2329,84 @@ void setupSpriteUseVis( ObjectRecord *inObject, int inUsesRemaining, int d = inUsesRemaining; - // hide some sprites - - int numSpritesLeft = - ( d * (numVanishingSprites) ) / numUses; - - int numInLastDummy = numVanishingSprites / numUses; + - if( numInLastDummy == 0 ) { - // add 1 to everything to pad up, so last - // dummy has 1 sprite in it - numSpritesLeft += 1; - } - + // hide some vanishing sprites + if( numVanishingSprites > 0 ) { + + int numSpritesLeft = + ( d * (numVanishingSprites) ) / numUses; + + int numInLastDummy = numVanishingSprites / numUses; + + int numInFirstDummy = ( ( numUses - 1 ) * + numVanishingSprites ) / numUses; + + if( numInLastDummy == 0 ) { + // add 1 to everything to pad up, so last + // dummy has 1 sprite in it + numSpritesLeft += 1; + + numInFirstDummy += 1; + } - if( numSpritesLeft > numVanishingSprites ) { - numSpritesLeft = numVanishingSprites; - } + if( numSpritesLeft > numVanishingSprites ) { + numSpritesLeft = numVanishingSprites; + } + if( numInFirstDummy > numVanishingSprites ) { + numInFirstDummy = numVanishingSprites; + } + - for( int v=numSpritesLeft; v 1 ) { + numSpritesLeft --; + } + } + - inSpriteSkipDrawing[ vanishingIndices.getElementDirect( v ) ] = - true; + for( int v=numSpritesLeft; v numAppearingSprites ) { - numInvisSpritesLeft = numAppearingSprites; + if( numAppearingSprites > 0 ) { + + int numInvisSpritesLeft = + lrint( ( d * (numAppearingSprites) ) / (double)numUses ); + + /* + // testing... do we need to do this? + int numInvisInLastDummy = numAppearingSprites / numUses; + + if( numInLastDummy == 0 ) { + // add 1 to everything to pad up, so last + // dummy has 1 sprite in it + numSpritesLeft += 1; } - - for( int v=0; v numAppearingSprites ) { + numInvisSpritesLeft = numAppearingSprites; + } - inSpriteSkipDrawing[ appearingIndices.getElementDirect( v ) ] = - false; + for( int v=0; vexists() ) { + + char *nextNumberOffsetString = + nextNumberOffsetFile->readFileContents(); + + if( nextNumberOffsetString != NULL ) { + sscanf( nextNumberOffsetString, "%d", &nextObjectNumberOffset ); + + nextObjectNumber += nextObjectNumberOffset; + + delete [] nextNumberOffsetString; + } + } if( newID == -1 ) { newID = nextObjectNumber; @@ -3305,20 +3365,38 @@ int addObject( const char *inDescription, delete objectFile; if( inReplaceID == -1 ) { - nextObjectNumber++; - - - char *nextNumberString = autoSprintf( "%d", nextObjectNumber ); - - File *nextNumberFile = - objectsDir.getChildFile( "nextObjectNumber.txt" ); + if( nextObjectNumberOffset > 0 ) { + nextObjectNumberOffset++; + - nextNumberFile->writeToFile( nextNumberString ); + char *nextNumberOffsetString = autoSprintf( "%d", nextObjectNumberOffset ); - delete [] nextNumberString; + File *nextNumberOffsetFile = + objectsDir.getChildFile( "nextObjectNumberOffset.txt" ); + + nextNumberOffsetFile->writeToFile( nextNumberOffsetString ); + + delete [] nextNumberOffsetString; + + + delete nextNumberOffsetFile; + } + else { + nextObjectNumber++; + + char *nextNumberString = autoSprintf( "%d", nextObjectNumber ); - delete nextNumberFile; + File *nextNumberFile = + objectsDir.getChildFile( "nextObjectNumber.txt" ); + + nextNumberFile->writeToFile( nextNumberString ); + + delete [] nextNumberString; + + + delete nextNumberFile; + } } } @@ -3593,6 +3671,8 @@ int addObject( const char *inDescription, setupOwned( r ); setupNoHighlight( r ); + + setupNoClickThrough( r ); setupMaxPickupAge( r ); diff --git a/gameSource/objectBank.h b/gameSource/objectBank.h index 60bf0e864..12ed1d881 100644 --- a/gameSource/objectBank.h +++ b/gameSource/objectBank.h @@ -414,6 +414,10 @@ typedef struct ObjectRecord { char noHighlight; + // tall objects can be clicked through to reach small objects behind + // this property disables that + char noClickThrough; + // for auto-orienting fences, walls, etc // all three objects know the IDs of all three objects int horizontalVersionID; diff --git a/gameSource/photos.cpp b/gameSource/photos.cpp index ad639565b..992500d46 100644 --- a/gameSource/photos.cpp +++ b/gameSource/photos.cpp @@ -14,16 +14,28 @@ #include "minorGems/graphics/filters/FastBlurFilter.h" #include "minorGems/graphics/filters/BoxBlurFilter.h" +#include "minorGems/graphics/RGBAImage.h" + #define STB_IMAGE_WRITE_IMPLEMENTATION #include "stb_image_write.h" +#define STB_IMAGE_RESIZE_IMPLEMENTATION + +#include "stb_image_resize.h" + + extern char *userEmail; extern char *serverIP; extern int ourID; +extern double viewWidth; +extern double viewHeight; +extern double visibleViewWidth; + + Image *photoBorder; @@ -151,10 +163,16 @@ static inline double intDist( int inXA, int inYA, int inXB, int inYB ) { + +// inCameraLocation is in world pixels relative to screen center +// (World pixel units assume CELL_D pixels per tile). +// Note that world pixels may differ from screen pixels if screen +// size does not match our native game resolution + // Don't muck with this code in a way that tricks the photo server. // read OneLife/photoServer/protocol.txt for your very serious warning // about this. -void takePhoto( doublePair inCamerLocation, int inCameraFacing, +void takePhoto( doublePair inCameraLocation, int inCameraFacing, int inSequenceNumber, char *inServerSig, int inAuthorID, @@ -172,30 +190,75 @@ void takePhoto( doublePair inCamerLocation, int inCameraFacing, int screenWidth, screenHeight; getScreenDimensions( &screenWidth, &screenHeight ); - int rectStartX = lrint( inCamerLocation.x ); + double targetAspectRatio = visibleViewWidth / (double)viewHeight; + + double screenAspectRatio = screenWidth / (double)screenHeight; + + double screenScale; + + if( screenAspectRatio > targetAspectRatio ) { + screenScale = screenHeight / (double) viewHeight; + } + else { + screenScale = screenWidth / visibleViewWidth; + } + + // now we can convert cam position into screen pixels, relative to center of + // screen + inCameraLocation = mult( inCameraLocation, screenScale ); + + // now that it's in screen pixels, we can compute it relative to + // corner of screen + inCameraLocation.x += screenWidth / 2; + inCameraLocation.y += screenHeight / 2; + + + int rectStartX = lrint( inCameraLocation.x ); if( inCameraFacing == -1 ) { - rectStartX -= 400 + CELL_D / 2; + rectStartX -= screenScale * ( 400 + CELL_D / 2 ); } else { - rectStartX += CELL_D / 2; + rectStartX += screenScale * ( CELL_D / 2 ); } if( rectStartX < 0 ) { rectStartX = 0; } - if( rectStartX >= screenWidth - 400 ) { - rectStartX = screenWidth - 401; + if( rectStartX >= screenWidth - screenScale * 400 ) { + rectStartX = screenWidth - screenScale * 400 - 1; } - int rectStartY = lrint( inCamerLocation.y ) - CELL_D; + int rectStartY = lrint( inCameraLocation.y ) - screenScale * CELL_D; if( rectStartY < 0 ) { rectStartY = 0; } - if( rectStartY >= screenHeight - 400 ) { - rectStartY = screenHeight - 401; + if( rectStartY >= screenHeight - screenScale * 400 ) { + rectStartY = screenHeight - screenScale * 400 - 1; } + + Image *im = getScreenRegionRaw( rectStartX, rectStartY, + screenScale * 400, screenScale * 400 ); + + if( screenScale != 1.0 ) { + // resize the image to 400x400 - Image *im = getScreenRegionRaw( rectStartX, rectStartY, 400, 400 ); + unsigned char *bytes = RGBAImage::getRGBABytes( im ); + + unsigned char *outBytes = new unsigned char[ 400 * 400 * 4 ]; + + int result = stbir_resize_uint8( bytes, + im->getWidth(), im->getHeight(), + 0, outBytes, 400, 400, 0, 4 ); + if( result == 1 ) { + delete im; + + im = RGBAImage::getImageFromBytes( outBytes, 400, 400, 4 ); + } + + delete [] bytes; + delete [] outBytes; + } + double *r = im->getChannel( 0 ); double *g = im->getChannel( 1 ); diff --git a/gameSource/settings/possibleFrameRates.ini b/gameSource/settings/possibleFrameRates.ini index 62c34d958..1f63df298 100644 --- a/gameSource/settings/possibleFrameRates.ini +++ b/gameSource/settings/possibleFrameRates.ini @@ -2,4 +2,5 @@ 60 100 120 -144 \ No newline at end of file +144 +165 \ No newline at end of file diff --git a/gameSource/spriteBank.cpp b/gameSource/spriteBank.cpp index 85b546b34..a9306adb1 100644 --- a/gameSource/spriteBank.cpp +++ b/gameSource/spriteBank.cpp @@ -84,7 +84,8 @@ void enableSpriteSearch( char inEnable ) { // not bulk data tga files) static char shouldFileBeCached( char *inFileName ) { if( strstr( inFileName, ".txt" ) != NULL && - strcmp( inFileName, "nextSpriteNumber.txt" ) != 0 ) { + strcmp( inFileName, "nextSpriteNumber.txt" ) != 0 && + strcmp( inFileName, "nextSpriteNumberOffset.txt" ) != 0 ) { return true; } return false; @@ -1013,6 +1014,7 @@ int addSprite( const char *inTag, SpriteHandle inSprite, int nextSpriteNumber = 1; + int nextSpriteNumberOffset = 0; File *nextNumberFile = spritesDir.getChildFile( "nextSpriteNumber.txt" ); @@ -1028,6 +1030,23 @@ int addSprite( const char *inTag, SpriteHandle inSprite, delete [] nextNumberString; } } + + File *nextNumberOffsetFile = + spritesDir.getChildFile( "nextSpriteNumberOffset.txt" ); + + if( nextNumberOffsetFile->exists() ) { + + char *nextNumberOffsetString = + nextNumberOffsetFile->readFileContents(); + + if( nextNumberOffsetString != NULL ) { + sscanf( nextNumberOffsetString, "%d", &nextSpriteNumberOffset ); + + nextSpriteNumber += nextSpriteNumberOffset; + + delete [] nextNumberOffsetString; + } + } @@ -1069,18 +1088,34 @@ int addSprite( const char *inTag, SpriteHandle inSprite, delete [] fileNameTXT; delete metaFile; - nextSpriteNumber++; - + if( nextSpriteNumberOffset > 0 ) { + nextSpriteNumberOffset++; + - - char *nextNumberString = autoSprintf( "%d", nextSpriteNumber ); - - nextNumberFile->writeToFile( nextNumberString ); - - delete [] nextNumberString; - - - delete nextNumberFile; + + char *nextNumberOffsetString = autoSprintf( "%d", nextSpriteNumberOffset ); + + nextNumberOffsetFile->writeToFile( nextNumberOffsetString ); + + delete [] nextNumberOffsetString; + + + delete nextNumberOffsetFile; + } + else { + nextSpriteNumber++; + + + + char *nextNumberString = autoSprintf( "%d", nextSpriteNumber ); + + nextNumberFile->writeToFile( nextNumberString ); + + delete [] nextNumberString; + + + delete nextNumberFile; + } } if( newID == -1 ) { diff --git a/gameSource/stb_image_resize.h b/gameSource/stb_image_resize.h new file mode 100644 index 000000000..ef9e6fe87 --- /dev/null +++ b/gameSource/stb_image_resize.h @@ -0,0 +1,2634 @@ +/* stb_image_resize - v0.97 - public domain image resizing + by Jorge L Rodriguez (@VinoBS) - 2014 + http://github.com/nothings/stb + + Written with emphasis on usability, portability, and efficiency. (No + SIMD or threads, so it be easily outperformed by libs that use those.) + Only scaling and translation is supported, no rotations or shears. + Easy API downsamples w/Mitchell filter, upsamples w/cubic interpolation. + + COMPILING & LINKING + In one C/C++ file that #includes this file, do this: + #define STB_IMAGE_RESIZE_IMPLEMENTATION + before the #include. That will create the implementation in that file. + + QUICKSTART + stbir_resize_uint8( input_pixels , in_w , in_h , 0, + output_pixels, out_w, out_h, 0, num_channels) + stbir_resize_float(...) + stbir_resize_uint8_srgb( input_pixels , in_w , in_h , 0, + output_pixels, out_w, out_h, 0, + num_channels , alpha_chan , 0) + stbir_resize_uint8_srgb_edgemode( + input_pixels , in_w , in_h , 0, + output_pixels, out_w, out_h, 0, + num_channels , alpha_chan , 0, STBIR_EDGE_CLAMP) + // WRAP/REFLECT/ZERO + + FULL API + See the "header file" section of the source for API documentation. + + ADDITIONAL DOCUMENTATION + + SRGB & FLOATING POINT REPRESENTATION + The sRGB functions presume IEEE floating point. If you do not have + IEEE floating point, define STBIR_NON_IEEE_FLOAT. This will use + a slower implementation. + + MEMORY ALLOCATION + The resize functions here perform a single memory allocation using + malloc. To control the memory allocation, before the #include that + triggers the implementation, do: + + #define STBIR_MALLOC(size,context) ... + #define STBIR_FREE(ptr,context) ... + + Each resize function makes exactly one call to malloc/free, so to use + temp memory, store the temp memory in the context and return that. + + ASSERT + Define STBIR_ASSERT(boolval) to override assert() and not use assert.h + + OPTIMIZATION + Define STBIR_SATURATE_INT to compute clamp values in-range using + integer operations instead of float operations. This may be faster + on some platforms. + + DEFAULT FILTERS + For functions which don't provide explicit control over what filters + to use, you can change the compile-time defaults with + + #define STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_something + #define STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_something + + See stbir_filter in the header-file section for the list of filters. + + NEW FILTERS + A number of 1D filter kernels are used. For a list of + supported filters see the stbir_filter enum. To add a new filter, + write a filter function and add it to stbir__filter_info_table. + + PROGRESS + For interactive use with slow resize operations, you can install + a progress-report callback: + + #define STBIR_PROGRESS_REPORT(val) some_func(val) + + The parameter val is a float which goes from 0 to 1 as progress is made. + + For example: + + static void my_progress_report(float progress); + #define STBIR_PROGRESS_REPORT(val) my_progress_report(val) + + #define STB_IMAGE_RESIZE_IMPLEMENTATION + #include "stb_image_resize.h" + + static void my_progress_report(float progress) + { + printf("Progress: %f%%\n", progress*100); + } + + MAX CHANNELS + If your image has more than 64 channels, define STBIR_MAX_CHANNELS + to the max you'll have. + + ALPHA CHANNEL + Most of the resizing functions provide the ability to control how + the alpha channel of an image is processed. The important things + to know about this: + + 1. The best mathematically-behaved version of alpha to use is + called "premultiplied alpha", in which the other color channels + have had the alpha value multiplied in. If you use premultiplied + alpha, linear filtering (such as image resampling done by this + library, or performed in texture units on GPUs) does the "right + thing". While premultiplied alpha is standard in the movie CGI + industry, it is still uncommon in the videogame/real-time world. + + If you linearly filter non-premultiplied alpha, strange effects + occur. (For example, the 50/50 average of 99% transparent bright green + and 1% transparent black produces 50% transparent dark green when + non-premultiplied, whereas premultiplied it produces 50% + transparent near-black. The former introduces green energy + that doesn't exist in the source image.) + + 2. Artists should not edit premultiplied-alpha images; artists + want non-premultiplied alpha images. Thus, art tools generally output + non-premultiplied alpha images. + + 3. You will get best results in most cases by converting images + to premultiplied alpha before processing them mathematically. + + 4. If you pass the flag STBIR_FLAG_ALPHA_PREMULTIPLIED, the + resizer does not do anything special for the alpha channel; + it is resampled identically to other channels. This produces + the correct results for premultiplied-alpha images, but produces + less-than-ideal results for non-premultiplied-alpha images. + + 5. If you do not pass the flag STBIR_FLAG_ALPHA_PREMULTIPLIED, + then the resizer weights the contribution of input pixels + based on their alpha values, or, equivalently, it multiplies + the alpha value into the color channels, resamples, then divides + by the resultant alpha value. Input pixels which have alpha=0 do + not contribute at all to output pixels unless _all_ of the input + pixels affecting that output pixel have alpha=0, in which case + the result for that pixel is the same as it would be without + STBIR_FLAG_ALPHA_PREMULTIPLIED. However, this is only true for + input images in integer formats. For input images in float format, + input pixels with alpha=0 have no effect, and output pixels + which have alpha=0 will be 0 in all channels. (For float images, + you can manually achieve the same result by adding a tiny epsilon + value to the alpha channel of every image, and then subtracting + or clamping it at the end.) + + 6. You can suppress the behavior described in #5 and make + all-0-alpha pixels have 0 in all channels by #defining + STBIR_NO_ALPHA_EPSILON. + + 7. You can separately control whether the alpha channel is + interpreted as linear or affected by the colorspace. By default + it is linear; you almost never want to apply the colorspace. + (For example, graphics hardware does not apply sRGB conversion + to the alpha channel.) + + CONTRIBUTORS + Jorge L Rodriguez: Implementation + Sean Barrett: API design, optimizations + Aras Pranckevicius: bugfix + Nathan Reed: warning fixes + + REVISIONS + 0.97 (2020-02-02) fixed warning + 0.96 (2019-03-04) fixed warnings + 0.95 (2017-07-23) fixed warnings + 0.94 (2017-03-18) fixed warnings + 0.93 (2017-03-03) fixed bug with certain combinations of heights + 0.92 (2017-01-02) fix integer overflow on large (>2GB) images + 0.91 (2016-04-02) fix warnings; fix handling of subpixel regions + 0.90 (2014-09-17) first released version + + LICENSE + See end of file for license information. + + TODO + Don't decode all of the image data when only processing a partial tile + Don't use full-width decode buffers when only processing a partial tile + When processing wide images, break processing into tiles so data fits in L1 cache + Installable filters? + Resize that respects alpha test coverage + (Reference code: FloatImage::alphaTestCoverage and FloatImage::scaleAlphaToCoverage: + https://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvimage/FloatImage.cpp ) +*/ + +#ifndef STBIR_INCLUDE_STB_IMAGE_RESIZE_H +#define STBIR_INCLUDE_STB_IMAGE_RESIZE_H + +#ifdef _MSC_VER +typedef unsigned char stbir_uint8; +typedef unsigned short stbir_uint16; +typedef unsigned int stbir_uint32; +#else +#include +typedef uint8_t stbir_uint8; +typedef uint16_t stbir_uint16; +typedef uint32_t stbir_uint32; +#endif + +#ifndef STBIRDEF +#ifdef STB_IMAGE_RESIZE_STATIC +#define STBIRDEF static +#else +#ifdef __cplusplus +#define STBIRDEF extern "C" +#else +#define STBIRDEF extern +#endif +#endif +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// Easy-to-use API: +// +// * "input pixels" points to an array of image data with 'num_channels' channels (e.g. RGB=3, RGBA=4) +// * input_w is input image width (x-axis), input_h is input image height (y-axis) +// * stride is the offset between successive rows of image data in memory, in bytes. you can +// specify 0 to mean packed continuously in memory +// * alpha channel is treated identically to other channels. +// * colorspace is linear or sRGB as specified by function name +// * returned result is 1 for success or 0 in case of an error. +// #define STBIR_ASSERT() to trigger an assert on parameter validation errors. +// * Memory required grows approximately linearly with input and output size, but with +// discontinuities at input_w == output_w and input_h == output_h. +// * These functions use a "default" resampling filter defined at compile time. To change the filter, +// you can change the compile-time defaults by #defining STBIR_DEFAULT_FILTER_UPSAMPLE +// and STBIR_DEFAULT_FILTER_DOWNSAMPLE, or you can use the medium-complexity API. + +STBIRDEF int stbir_resize_uint8( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels); + +STBIRDEF int stbir_resize_float( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + float *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels); + + +// The following functions interpret image data as gamma-corrected sRGB. +// Specify STBIR_ALPHA_CHANNEL_NONE if you have no alpha channel, +// or otherwise provide the index of the alpha channel. Flags value +// of 0 will probably do the right thing if you're not sure what +// the flags mean. + +#define STBIR_ALPHA_CHANNEL_NONE -1 + +// Set this flag if your texture has premultiplied alpha. Otherwise, stbir will +// use alpha-weighted resampling (effectively premultiplying, resampling, +// then unpremultiplying). +#define STBIR_FLAG_ALPHA_PREMULTIPLIED (1 << 0) +// The specified alpha channel should be handled as gamma-corrected value even +// when doing sRGB operations. +#define STBIR_FLAG_ALPHA_USES_COLORSPACE (1 << 1) + +STBIRDEF int stbir_resize_uint8_srgb(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags); + + +typedef enum +{ + STBIR_EDGE_CLAMP = 1, + STBIR_EDGE_REFLECT = 2, + STBIR_EDGE_WRAP = 3, + STBIR_EDGE_ZERO = 4, +} stbir_edge; + +// This function adds the ability to specify how requests to sample off the edge of the image are handled. +STBIRDEF int stbir_resize_uint8_srgb_edgemode(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode); + +////////////////////////////////////////////////////////////////////////////// +// +// Medium-complexity API +// +// This extends the easy-to-use API as follows: +// +// * Alpha-channel can be processed separately +// * If alpha_channel is not STBIR_ALPHA_CHANNEL_NONE +// * Alpha channel will not be gamma corrected (unless flags&STBIR_FLAG_GAMMA_CORRECT) +// * Filters will be weighted by alpha channel (unless flags&STBIR_FLAG_ALPHA_PREMULTIPLIED) +// * Filter can be selected explicitly +// * uint16 image type +// * sRGB colorspace available for all types +// * context parameter for passing to STBIR_MALLOC + +typedef enum +{ + STBIR_FILTER_DEFAULT = 0, // use same filter type that easy-to-use API chooses + STBIR_FILTER_BOX = 1, // A trapezoid w/1-pixel wide ramps, same result as box for integer scale ratios + STBIR_FILTER_TRIANGLE = 2, // On upsampling, produces same results as bilinear texture filtering + STBIR_FILTER_CUBICBSPLINE = 3, // The cubic b-spline (aka Mitchell-Netrevalli with B=1,C=0), gaussian-esque + STBIR_FILTER_CATMULLROM = 4, // An interpolating cubic spline + STBIR_FILTER_MITCHELL = 5, // Mitchell-Netrevalli filter with B=1/3, C=1/3 +} stbir_filter; + +typedef enum +{ + STBIR_COLORSPACE_LINEAR, + STBIR_COLORSPACE_SRGB, + + STBIR_MAX_COLORSPACES, +} stbir_colorspace; + +// The following functions are all identical except for the type of the image data + +STBIRDEF int stbir_resize_uint8_generic( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context); + +STBIRDEF int stbir_resize_uint16_generic(const stbir_uint16 *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + stbir_uint16 *output_pixels , int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context); + +STBIRDEF int stbir_resize_float_generic( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + float *output_pixels , int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context); + + + +////////////////////////////////////////////////////////////////////////////// +// +// Full-complexity API +// +// This extends the medium API as follows: +// +// * uint32 image type +// * not typesafe +// * separate filter types for each axis +// * separate edge modes for each axis +// * can specify scale explicitly for subpixel correctness +// * can specify image source tile using texture coordinates + +typedef enum +{ + STBIR_TYPE_UINT8 , + STBIR_TYPE_UINT16, + STBIR_TYPE_UINT32, + STBIR_TYPE_FLOAT , + + STBIR_MAX_TYPES +} stbir_datatype; + +STBIRDEF int stbir_resize( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context); + +STBIRDEF int stbir_resize_subpixel(const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context, + float x_scale, float y_scale, + float x_offset, float y_offset); + +STBIRDEF int stbir_resize_region( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context, + float s0, float t0, float s1, float t1); +// (s0, t0) & (s1, t1) are the top-left and bottom right corner (uv addressing style: [0, 1]x[0, 1]) of a region of the input image to use. + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBIR_INCLUDE_STB_IMAGE_RESIZE_H + + + + + +#ifdef STB_IMAGE_RESIZE_IMPLEMENTATION + +#ifndef STBIR_ASSERT +#include +#define STBIR_ASSERT(x) assert(x) +#endif + +// For memset +#include + +#include + +#ifndef STBIR_MALLOC +#include +// use comma operator to evaluate c, to avoid "unused parameter" warnings +#define STBIR_MALLOC(size,c) ((void)(c), malloc(size)) +#define STBIR_FREE(ptr,c) ((void)(c), free(ptr)) +#endif + +#ifndef _MSC_VER +#ifdef __cplusplus +#define stbir__inline inline +#else +#define stbir__inline +#endif +#else +#define stbir__inline __forceinline +#endif + + +// should produce compiler error if size is wrong +typedef unsigned char stbir__validate_uint32[sizeof(stbir_uint32) == 4 ? 1 : -1]; + +#ifdef _MSC_VER +#define STBIR__NOTUSED(v) (void)(v) +#else +#define STBIR__NOTUSED(v) (void)sizeof(v) +#endif + +#define STBIR__ARRAY_SIZE(a) (sizeof((a))/sizeof((a)[0])) + +#ifndef STBIR_DEFAULT_FILTER_UPSAMPLE +#define STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_CATMULLROM +#endif + +#ifndef STBIR_DEFAULT_FILTER_DOWNSAMPLE +#define STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_MITCHELL +#endif + +#ifndef STBIR_PROGRESS_REPORT +#define STBIR_PROGRESS_REPORT(float_0_to_1) +#endif + +#ifndef STBIR_MAX_CHANNELS +#define STBIR_MAX_CHANNELS 64 +#endif + +#if STBIR_MAX_CHANNELS > 65536 +#error "Too many channels; STBIR_MAX_CHANNELS must be no more than 65536." +// because we store the indices in 16-bit variables +#endif + +// This value is added to alpha just before premultiplication to avoid +// zeroing out color values. It is equivalent to 2^-80. If you don't want +// that behavior (it may interfere if you have floating point images with +// very small alpha values) then you can define STBIR_NO_ALPHA_EPSILON to +// disable it. +#ifndef STBIR_ALPHA_EPSILON +#define STBIR_ALPHA_EPSILON ((float)1 / (1 << 20) / (1 << 20) / (1 << 20) / (1 << 20)) +#endif + + + +#ifdef _MSC_VER +#define STBIR__UNUSED_PARAM(v) (void)(v) +#else +#define STBIR__UNUSED_PARAM(v) (void)sizeof(v) +#endif + +// must match stbir_datatype +static unsigned char stbir__type_size[] = { + 1, // STBIR_TYPE_UINT8 + 2, // STBIR_TYPE_UINT16 + 4, // STBIR_TYPE_UINT32 + 4, // STBIR_TYPE_FLOAT +}; + +// Kernel function centered at 0 +typedef float (stbir__kernel_fn)(float x, float scale); +typedef float (stbir__support_fn)(float scale); + +typedef struct +{ + stbir__kernel_fn* kernel; + stbir__support_fn* support; +} stbir__filter_info; + +// When upsampling, the contributors are which source pixels contribute. +// When downsampling, the contributors are which destination pixels are contributed to. +typedef struct +{ + int n0; // First contributing pixel + int n1; // Last contributing pixel +} stbir__contributors; + +typedef struct +{ + const void* input_data; + int input_w; + int input_h; + int input_stride_bytes; + + void* output_data; + int output_w; + int output_h; + int output_stride_bytes; + + float s0, t0, s1, t1; + + float horizontal_shift; // Units: output pixels + float vertical_shift; // Units: output pixels + float horizontal_scale; + float vertical_scale; + + int channels; + int alpha_channel; + stbir_uint32 flags; + stbir_datatype type; + stbir_filter horizontal_filter; + stbir_filter vertical_filter; + stbir_edge edge_horizontal; + stbir_edge edge_vertical; + stbir_colorspace colorspace; + + stbir__contributors* horizontal_contributors; + float* horizontal_coefficients; + + stbir__contributors* vertical_contributors; + float* vertical_coefficients; + + int decode_buffer_pixels; + float* decode_buffer; + + float* horizontal_buffer; + + // cache these because ceil/floor are inexplicably showing up in profile + int horizontal_coefficient_width; + int vertical_coefficient_width; + int horizontal_filter_pixel_width; + int vertical_filter_pixel_width; + int horizontal_filter_pixel_margin; + int vertical_filter_pixel_margin; + int horizontal_num_contributors; + int vertical_num_contributors; + + int ring_buffer_length_bytes; // The length of an individual entry in the ring buffer. The total number of ring buffers is stbir__get_filter_pixel_width(filter) + int ring_buffer_num_entries; // Total number of entries in the ring buffer. + int ring_buffer_first_scanline; + int ring_buffer_last_scanline; + int ring_buffer_begin_index; // first_scanline is at this index in the ring buffer + float* ring_buffer; + + float* encode_buffer; // A temporary buffer to store floats so we don't lose precision while we do multiply-adds. + + int horizontal_contributors_size; + int horizontal_coefficients_size; + int vertical_contributors_size; + int vertical_coefficients_size; + int decode_buffer_size; + int horizontal_buffer_size; + int ring_buffer_size; + int encode_buffer_size; +} stbir__info; + + +static const float stbir__max_uint8_as_float = 255.0f; +static const float stbir__max_uint16_as_float = 65535.0f; +static const double stbir__max_uint32_as_float = 4294967295.0; + + +static stbir__inline int stbir__min(int a, int b) +{ + return a < b ? a : b; +} + +static stbir__inline float stbir__saturate(float x) +{ + if (x < 0) + return 0; + + if (x > 1) + return 1; + + return x; +} + +#ifdef STBIR_SATURATE_INT +static stbir__inline stbir_uint8 stbir__saturate8(int x) +{ + if ((unsigned int) x <= 255) + return x; + + if (x < 0) + return 0; + + return 255; +} + +static stbir__inline stbir_uint16 stbir__saturate16(int x) +{ + if ((unsigned int) x <= 65535) + return x; + + if (x < 0) + return 0; + + return 65535; +} +#endif + +static float stbir__srgb_uchar_to_linear_float[256] = { + 0.000000f, 0.000304f, 0.000607f, 0.000911f, 0.001214f, 0.001518f, 0.001821f, 0.002125f, 0.002428f, 0.002732f, 0.003035f, + 0.003347f, 0.003677f, 0.004025f, 0.004391f, 0.004777f, 0.005182f, 0.005605f, 0.006049f, 0.006512f, 0.006995f, 0.007499f, + 0.008023f, 0.008568f, 0.009134f, 0.009721f, 0.010330f, 0.010960f, 0.011612f, 0.012286f, 0.012983f, 0.013702f, 0.014444f, + 0.015209f, 0.015996f, 0.016807f, 0.017642f, 0.018500f, 0.019382f, 0.020289f, 0.021219f, 0.022174f, 0.023153f, 0.024158f, + 0.025187f, 0.026241f, 0.027321f, 0.028426f, 0.029557f, 0.030713f, 0.031896f, 0.033105f, 0.034340f, 0.035601f, 0.036889f, + 0.038204f, 0.039546f, 0.040915f, 0.042311f, 0.043735f, 0.045186f, 0.046665f, 0.048172f, 0.049707f, 0.051269f, 0.052861f, + 0.054480f, 0.056128f, 0.057805f, 0.059511f, 0.061246f, 0.063010f, 0.064803f, 0.066626f, 0.068478f, 0.070360f, 0.072272f, + 0.074214f, 0.076185f, 0.078187f, 0.080220f, 0.082283f, 0.084376f, 0.086500f, 0.088656f, 0.090842f, 0.093059f, 0.095307f, + 0.097587f, 0.099899f, 0.102242f, 0.104616f, 0.107023f, 0.109462f, 0.111932f, 0.114435f, 0.116971f, 0.119538f, 0.122139f, + 0.124772f, 0.127438f, 0.130136f, 0.132868f, 0.135633f, 0.138432f, 0.141263f, 0.144128f, 0.147027f, 0.149960f, 0.152926f, + 0.155926f, 0.158961f, 0.162029f, 0.165132f, 0.168269f, 0.171441f, 0.174647f, 0.177888f, 0.181164f, 0.184475f, 0.187821f, + 0.191202f, 0.194618f, 0.198069f, 0.201556f, 0.205079f, 0.208637f, 0.212231f, 0.215861f, 0.219526f, 0.223228f, 0.226966f, + 0.230740f, 0.234551f, 0.238398f, 0.242281f, 0.246201f, 0.250158f, 0.254152f, 0.258183f, 0.262251f, 0.266356f, 0.270498f, + 0.274677f, 0.278894f, 0.283149f, 0.287441f, 0.291771f, 0.296138f, 0.300544f, 0.304987f, 0.309469f, 0.313989f, 0.318547f, + 0.323143f, 0.327778f, 0.332452f, 0.337164f, 0.341914f, 0.346704f, 0.351533f, 0.356400f, 0.361307f, 0.366253f, 0.371238f, + 0.376262f, 0.381326f, 0.386430f, 0.391573f, 0.396755f, 0.401978f, 0.407240f, 0.412543f, 0.417885f, 0.423268f, 0.428691f, + 0.434154f, 0.439657f, 0.445201f, 0.450786f, 0.456411f, 0.462077f, 0.467784f, 0.473532f, 0.479320f, 0.485150f, 0.491021f, + 0.496933f, 0.502887f, 0.508881f, 0.514918f, 0.520996f, 0.527115f, 0.533276f, 0.539480f, 0.545725f, 0.552011f, 0.558340f, + 0.564712f, 0.571125f, 0.577581f, 0.584078f, 0.590619f, 0.597202f, 0.603827f, 0.610496f, 0.617207f, 0.623960f, 0.630757f, + 0.637597f, 0.644480f, 0.651406f, 0.658375f, 0.665387f, 0.672443f, 0.679543f, 0.686685f, 0.693872f, 0.701102f, 0.708376f, + 0.715694f, 0.723055f, 0.730461f, 0.737911f, 0.745404f, 0.752942f, 0.760525f, 0.768151f, 0.775822f, 0.783538f, 0.791298f, + 0.799103f, 0.806952f, 0.814847f, 0.822786f, 0.830770f, 0.838799f, 0.846873f, 0.854993f, 0.863157f, 0.871367f, 0.879622f, + 0.887923f, 0.896269f, 0.904661f, 0.913099f, 0.921582f, 0.930111f, 0.938686f, 0.947307f, 0.955974f, 0.964686f, 0.973445f, + 0.982251f, 0.991102f, 1.0f +}; + +static float stbir__srgb_to_linear(float f) +{ + if (f <= 0.04045f) + return f / 12.92f; + else + return (float)pow((f + 0.055f) / 1.055f, 2.4f); +} + +static float stbir__linear_to_srgb(float f) +{ + if (f <= 0.0031308f) + return f * 12.92f; + else + return 1.055f * (float)pow(f, 1 / 2.4f) - 0.055f; +} + +#ifndef STBIR_NON_IEEE_FLOAT +// From https://gist.github.com/rygorous/2203834 + +typedef union +{ + stbir_uint32 u; + float f; +} stbir__FP32; + +static const stbir_uint32 fp32_to_srgb8_tab4[104] = { + 0x0073000d, 0x007a000d, 0x0080000d, 0x0087000d, 0x008d000d, 0x0094000d, 0x009a000d, 0x00a1000d, + 0x00a7001a, 0x00b4001a, 0x00c1001a, 0x00ce001a, 0x00da001a, 0x00e7001a, 0x00f4001a, 0x0101001a, + 0x010e0033, 0x01280033, 0x01410033, 0x015b0033, 0x01750033, 0x018f0033, 0x01a80033, 0x01c20033, + 0x01dc0067, 0x020f0067, 0x02430067, 0x02760067, 0x02aa0067, 0x02dd0067, 0x03110067, 0x03440067, + 0x037800ce, 0x03df00ce, 0x044600ce, 0x04ad00ce, 0x051400ce, 0x057b00c5, 0x05dd00bc, 0x063b00b5, + 0x06970158, 0x07420142, 0x07e30130, 0x087b0120, 0x090b0112, 0x09940106, 0x0a1700fc, 0x0a9500f2, + 0x0b0f01cb, 0x0bf401ae, 0x0ccb0195, 0x0d950180, 0x0e56016e, 0x0f0d015e, 0x0fbc0150, 0x10630143, + 0x11070264, 0x1238023e, 0x1357021d, 0x14660201, 0x156601e9, 0x165a01d3, 0x174401c0, 0x182401af, + 0x18fe0331, 0x1a9602fe, 0x1c1502d2, 0x1d7e02ad, 0x1ed4028d, 0x201a0270, 0x21520256, 0x227d0240, + 0x239f0443, 0x25c003fe, 0x27bf03c4, 0x29a10392, 0x2b6a0367, 0x2d1d0341, 0x2ebe031f, 0x304d0300, + 0x31d105b0, 0x34a80555, 0x37520507, 0x39d504c5, 0x3c37048b, 0x3e7c0458, 0x40a8042a, 0x42bd0401, + 0x44c20798, 0x488e071e, 0x4c1c06b6, 0x4f76065d, 0x52a50610, 0x55ac05cc, 0x5892058f, 0x5b590559, + 0x5e0c0a23, 0x631c0980, 0x67db08f6, 0x6c55087f, 0x70940818, 0x74a007bd, 0x787d076c, 0x7c330723, +}; + +static stbir_uint8 stbir__linear_to_srgb_uchar(float in) +{ + static const stbir__FP32 almostone = { 0x3f7fffff }; // 1-eps + static const stbir__FP32 minval = { (127-13) << 23 }; + stbir_uint32 tab,bias,scale,t; + stbir__FP32 f; + + // Clamp to [2^(-13), 1-eps]; these two values map to 0 and 1, respectively. + // The tests are carefully written so that NaNs map to 0, same as in the reference + // implementation. + if (!(in > minval.f)) // written this way to catch NaNs + in = minval.f; + if (in > almostone.f) + in = almostone.f; + + // Do the table lookup and unpack bias, scale + f.f = in; + tab = fp32_to_srgb8_tab4[(f.u - minval.u) >> 20]; + bias = (tab >> 16) << 9; + scale = tab & 0xffff; + + // Grab next-highest mantissa bits and perform linear interpolation + t = (f.u >> 12) & 0xff; + return (unsigned char) ((bias + scale*t) >> 16); +} + +#else +// sRGB transition values, scaled by 1<<28 +static int stbir__srgb_offset_to_linear_scaled[256] = +{ + 0, 40738, 122216, 203693, 285170, 366648, 448125, 529603, + 611080, 692557, 774035, 855852, 942009, 1033024, 1128971, 1229926, + 1335959, 1447142, 1563542, 1685229, 1812268, 1944725, 2082664, 2226148, + 2375238, 2529996, 2690481, 2856753, 3028870, 3206888, 3390865, 3580856, + 3776916, 3979100, 4187460, 4402049, 4622919, 4850123, 5083710, 5323731, + 5570236, 5823273, 6082892, 6349140, 6622065, 6901714, 7188133, 7481369, + 7781466, 8088471, 8402427, 8723380, 9051372, 9386448, 9728650, 10078021, + 10434603, 10798439, 11169569, 11548036, 11933879, 12327139, 12727857, 13136073, + 13551826, 13975156, 14406100, 14844697, 15290987, 15745007, 16206795, 16676389, + 17153826, 17639142, 18132374, 18633560, 19142734, 19659934, 20185196, 20718552, + 21260042, 21809696, 22367554, 22933648, 23508010, 24090680, 24681686, 25281066, + 25888850, 26505076, 27129772, 27762974, 28404716, 29055026, 29713942, 30381490, + 31057708, 31742624, 32436272, 33138682, 33849884, 34569912, 35298800, 36036568, + 36783260, 37538896, 38303512, 39077136, 39859796, 40651528, 41452360, 42262316, + 43081432, 43909732, 44747252, 45594016, 46450052, 47315392, 48190064, 49074096, + 49967516, 50870356, 51782636, 52704392, 53635648, 54576432, 55526772, 56486700, + 57456236, 58435408, 59424248, 60422780, 61431036, 62449032, 63476804, 64514376, + 65561776, 66619028, 67686160, 68763192, 69850160, 70947088, 72053992, 73170912, + 74297864, 75434880, 76581976, 77739184, 78906536, 80084040, 81271736, 82469648, + 83677792, 84896192, 86124888, 87363888, 88613232, 89872928, 91143016, 92423512, + 93714432, 95015816, 96327688, 97650056, 98982952, 100326408, 101680440, 103045072, + 104420320, 105806224, 107202800, 108610064, 110028048, 111456776, 112896264, 114346544, + 115807632, 117279552, 118762328, 120255976, 121760536, 123276016, 124802440, 126339832, + 127888216, 129447616, 131018048, 132599544, 134192112, 135795792, 137410592, 139036528, + 140673648, 142321952, 143981456, 145652208, 147334208, 149027488, 150732064, 152447968, + 154175200, 155913792, 157663776, 159425168, 161197984, 162982240, 164777968, 166585184, + 168403904, 170234160, 172075968, 173929344, 175794320, 177670896, 179559120, 181458992, + 183370528, 185293776, 187228736, 189175424, 191133888, 193104112, 195086128, 197079968, + 199085648, 201103184, 203132592, 205173888, 207227120, 209292272, 211369392, 213458480, + 215559568, 217672656, 219797792, 221934976, 224084240, 226245600, 228419056, 230604656, + 232802400, 235012320, 237234432, 239468736, 241715280, 243974080, 246245120, 248528464, + 250824112, 253132064, 255452368, 257785040, 260130080, 262487520, 264857376, 267239664, +}; + +static stbir_uint8 stbir__linear_to_srgb_uchar(float f) +{ + int x = (int) (f * (1 << 28)); // has headroom so you don't need to clamp + int v = 0; + int i; + + // Refine the guess with a short binary search. + i = v + 128; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 64; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 32; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 16; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 8; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 4; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 2; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 1; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + + return (stbir_uint8) v; +} +#endif + +static float stbir__filter_trapezoid(float x, float scale) +{ + float halfscale = scale / 2; + float t = 0.5f + halfscale; + STBIR_ASSERT(scale <= 1); + + x = (float)fabs(x); + + if (x >= t) + return 0; + else + { + float r = 0.5f - halfscale; + if (x <= r) + return 1; + else + return (t - x) / scale; + } +} + +static float stbir__support_trapezoid(float scale) +{ + STBIR_ASSERT(scale <= 1); + return 0.5f + scale / 2; +} + +static float stbir__filter_triangle(float x, float s) +{ + STBIR__UNUSED_PARAM(s); + + x = (float)fabs(x); + + if (x <= 1.0f) + return 1 - x; + else + return 0; +} + +static float stbir__filter_cubic(float x, float s) +{ + STBIR__UNUSED_PARAM(s); + + x = (float)fabs(x); + + if (x < 1.0f) + return (4 + x*x*(3*x - 6))/6; + else if (x < 2.0f) + return (8 + x*(-12 + x*(6 - x)))/6; + + return (0.0f); +} + +static float stbir__filter_catmullrom(float x, float s) +{ + STBIR__UNUSED_PARAM(s); + + x = (float)fabs(x); + + if (x < 1.0f) + return 1 - x*x*(2.5f - 1.5f*x); + else if (x < 2.0f) + return 2 - x*(4 + x*(0.5f*x - 2.5f)); + + return (0.0f); +} + +static float stbir__filter_mitchell(float x, float s) +{ + STBIR__UNUSED_PARAM(s); + + x = (float)fabs(x); + + if (x < 1.0f) + return (16 + x*x*(21 * x - 36))/18; + else if (x < 2.0f) + return (32 + x*(-60 + x*(36 - 7*x)))/18; + + return (0.0f); +} + +static float stbir__support_zero(float s) +{ + STBIR__UNUSED_PARAM(s); + return 0; +} + +static float stbir__support_one(float s) +{ + STBIR__UNUSED_PARAM(s); + return 1; +} + +static float stbir__support_two(float s) +{ + STBIR__UNUSED_PARAM(s); + return 2; +} + +static stbir__filter_info stbir__filter_info_table[] = { + { NULL, stbir__support_zero }, + { stbir__filter_trapezoid, stbir__support_trapezoid }, + { stbir__filter_triangle, stbir__support_one }, + { stbir__filter_cubic, stbir__support_two }, + { stbir__filter_catmullrom, stbir__support_two }, + { stbir__filter_mitchell, stbir__support_two }, +}; + +stbir__inline static int stbir__use_upsampling(float ratio) +{ + return ratio > 1; +} + +stbir__inline static int stbir__use_width_upsampling(stbir__info* stbir_info) +{ + return stbir__use_upsampling(stbir_info->horizontal_scale); +} + +stbir__inline static int stbir__use_height_upsampling(stbir__info* stbir_info) +{ + return stbir__use_upsampling(stbir_info->vertical_scale); +} + +// This is the maximum number of input samples that can affect an output sample +// with the given filter +static int stbir__get_filter_pixel_width(stbir_filter filter, float scale) +{ + STBIR_ASSERT(filter != 0); + STBIR_ASSERT(filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); + + if (stbir__use_upsampling(scale)) + return (int)ceil(stbir__filter_info_table[filter].support(1/scale) * 2); + else + return (int)ceil(stbir__filter_info_table[filter].support(scale) * 2 / scale); +} + +// This is how much to expand buffers to account for filters seeking outside +// the image boundaries. +static int stbir__get_filter_pixel_margin(stbir_filter filter, float scale) +{ + return stbir__get_filter_pixel_width(filter, scale) / 2; +} + +static int stbir__get_coefficient_width(stbir_filter filter, float scale) +{ + if (stbir__use_upsampling(scale)) + return (int)ceil(stbir__filter_info_table[filter].support(1 / scale) * 2); + else + return (int)ceil(stbir__filter_info_table[filter].support(scale) * 2); +} + +static int stbir__get_contributors(float scale, stbir_filter filter, int input_size, int output_size) +{ + if (stbir__use_upsampling(scale)) + return output_size; + else + return (input_size + stbir__get_filter_pixel_margin(filter, scale) * 2); +} + +static int stbir__get_total_horizontal_coefficients(stbir__info* info) +{ + return info->horizontal_num_contributors + * stbir__get_coefficient_width (info->horizontal_filter, info->horizontal_scale); +} + +static int stbir__get_total_vertical_coefficients(stbir__info* info) +{ + return info->vertical_num_contributors + * stbir__get_coefficient_width (info->vertical_filter, info->vertical_scale); +} + +static stbir__contributors* stbir__get_contributor(stbir__contributors* contributors, int n) +{ + return &contributors[n]; +} + +// For perf reasons this code is duplicated in stbir__resample_horizontal_upsample/downsample, +// if you change it here change it there too. +static float* stbir__get_coefficient(float* coefficients, stbir_filter filter, float scale, int n, int c) +{ + int width = stbir__get_coefficient_width(filter, scale); + return &coefficients[width*n + c]; +} + +static int stbir__edge_wrap_slow(stbir_edge edge, int n, int max) +{ + switch (edge) + { + case STBIR_EDGE_ZERO: + return 0; // we'll decode the wrong pixel here, and then overwrite with 0s later + + case STBIR_EDGE_CLAMP: + if (n < 0) + return 0; + + if (n >= max) + return max - 1; + + return n; // NOTREACHED + + case STBIR_EDGE_REFLECT: + { + if (n < 0) + { + if (n < max) + return -n; + else + return max - 1; + } + + if (n >= max) + { + int max2 = max * 2; + if (n >= max2) + return 0; + else + return max2 - n - 1; + } + + return n; // NOTREACHED + } + + case STBIR_EDGE_WRAP: + if (n >= 0) + return (n % max); + else + { + int m = (-n) % max; + + if (m != 0) + m = max - m; + + return (m); + } + // NOTREACHED + + default: + STBIR_ASSERT(!"Unimplemented edge type"); + return 0; + } +} + +stbir__inline static int stbir__edge_wrap(stbir_edge edge, int n, int max) +{ + // avoid per-pixel switch + if (n >= 0 && n < max) + return n; + return stbir__edge_wrap_slow(edge, n, max); +} + +// What input pixels contribute to this output pixel? +static void stbir__calculate_sample_range_upsample(int n, float out_filter_radius, float scale_ratio, float out_shift, int* in_first_pixel, int* in_last_pixel, float* in_center_of_out) +{ + float out_pixel_center = (float)n + 0.5f; + float out_pixel_influence_lowerbound = out_pixel_center - out_filter_radius; + float out_pixel_influence_upperbound = out_pixel_center + out_filter_radius; + + float in_pixel_influence_lowerbound = (out_pixel_influence_lowerbound + out_shift) / scale_ratio; + float in_pixel_influence_upperbound = (out_pixel_influence_upperbound + out_shift) / scale_ratio; + + *in_center_of_out = (out_pixel_center + out_shift) / scale_ratio; + *in_first_pixel = (int)(floor(in_pixel_influence_lowerbound + 0.5)); + *in_last_pixel = (int)(floor(in_pixel_influence_upperbound - 0.5)); +} + +// What output pixels does this input pixel contribute to? +static void stbir__calculate_sample_range_downsample(int n, float in_pixels_radius, float scale_ratio, float out_shift, int* out_first_pixel, int* out_last_pixel, float* out_center_of_in) +{ + float in_pixel_center = (float)n + 0.5f; + float in_pixel_influence_lowerbound = in_pixel_center - in_pixels_radius; + float in_pixel_influence_upperbound = in_pixel_center + in_pixels_radius; + + float out_pixel_influence_lowerbound = in_pixel_influence_lowerbound * scale_ratio - out_shift; + float out_pixel_influence_upperbound = in_pixel_influence_upperbound * scale_ratio - out_shift; + + *out_center_of_in = in_pixel_center * scale_ratio - out_shift; + *out_first_pixel = (int)(floor(out_pixel_influence_lowerbound + 0.5)); + *out_last_pixel = (int)(floor(out_pixel_influence_upperbound - 0.5)); +} + +static void stbir__calculate_coefficients_upsample(stbir_filter filter, float scale, int in_first_pixel, int in_last_pixel, float in_center_of_out, stbir__contributors* contributor, float* coefficient_group) +{ + int i; + float total_filter = 0; + float filter_scale; + + STBIR_ASSERT(in_last_pixel - in_first_pixel <= (int)ceil(stbir__filter_info_table[filter].support(1/scale) * 2)); // Taken directly from stbir__get_coefficient_width() which we can't call because we don't know if we're horizontal or vertical. + + contributor->n0 = in_first_pixel; + contributor->n1 = in_last_pixel; + + STBIR_ASSERT(contributor->n1 >= contributor->n0); + + for (i = 0; i <= in_last_pixel - in_first_pixel; i++) + { + float in_pixel_center = (float)(i + in_first_pixel) + 0.5f; + coefficient_group[i] = stbir__filter_info_table[filter].kernel(in_center_of_out - in_pixel_center, 1 / scale); + + // If the coefficient is zero, skip it. (Don't do the <0 check here, we want the influence of those outside pixels.) + if (i == 0 && !coefficient_group[i]) + { + contributor->n0 = ++in_first_pixel; + i--; + continue; + } + + total_filter += coefficient_group[i]; + } + + // NOTE(fg): Not actually true in general, nor is there any reason to expect it should be. + // It would be true in exact math but is at best approximately true in floating-point math, + // and it would not make sense to try and put actual bounds on this here because it depends + // on the image aspect ratio which can get pretty extreme. + //STBIR_ASSERT(stbir__filter_info_table[filter].kernel((float)(in_last_pixel + 1) + 0.5f - in_center_of_out, 1/scale) == 0); + + STBIR_ASSERT(total_filter > 0.9); + STBIR_ASSERT(total_filter < 1.1f); // Make sure it's not way off. + + // Make sure the sum of all coefficients is 1. + filter_scale = 1 / total_filter; + + for (i = 0; i <= in_last_pixel - in_first_pixel; i++) + coefficient_group[i] *= filter_scale; + + for (i = in_last_pixel - in_first_pixel; i >= 0; i--) + { + if (coefficient_group[i]) + break; + + // This line has no weight. We can skip it. + contributor->n1 = contributor->n0 + i - 1; + } +} + +static void stbir__calculate_coefficients_downsample(stbir_filter filter, float scale_ratio, int out_first_pixel, int out_last_pixel, float out_center_of_in, stbir__contributors* contributor, float* coefficient_group) +{ + int i; + + STBIR_ASSERT(out_last_pixel - out_first_pixel <= (int)ceil(stbir__filter_info_table[filter].support(scale_ratio) * 2)); // Taken directly from stbir__get_coefficient_width() which we can't call because we don't know if we're horizontal or vertical. + + contributor->n0 = out_first_pixel; + contributor->n1 = out_last_pixel; + + STBIR_ASSERT(contributor->n1 >= contributor->n0); + + for (i = 0; i <= out_last_pixel - out_first_pixel; i++) + { + float out_pixel_center = (float)(i + out_first_pixel) + 0.5f; + float x = out_pixel_center - out_center_of_in; + coefficient_group[i] = stbir__filter_info_table[filter].kernel(x, scale_ratio) * scale_ratio; + } + + // NOTE(fg): Not actually true in general, nor is there any reason to expect it should be. + // It would be true in exact math but is at best approximately true in floating-point math, + // and it would not make sense to try and put actual bounds on this here because it depends + // on the image aspect ratio which can get pretty extreme. + //STBIR_ASSERT(stbir__filter_info_table[filter].kernel((float)(out_last_pixel + 1) + 0.5f - out_center_of_in, scale_ratio) == 0); + + for (i = out_last_pixel - out_first_pixel; i >= 0; i--) + { + if (coefficient_group[i]) + break; + + // This line has no weight. We can skip it. + contributor->n1 = contributor->n0 + i - 1; + } +} + +static void stbir__normalize_downsample_coefficients(stbir__contributors* contributors, float* coefficients, stbir_filter filter, float scale_ratio, int input_size, int output_size) +{ + int num_contributors = stbir__get_contributors(scale_ratio, filter, input_size, output_size); + int num_coefficients = stbir__get_coefficient_width(filter, scale_ratio); + int i, j; + int skip; + + for (i = 0; i < output_size; i++) + { + float scale; + float total = 0; + + for (j = 0; j < num_contributors; j++) + { + if (i >= contributors[j].n0 && i <= contributors[j].n1) + { + float coefficient = *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i - contributors[j].n0); + total += coefficient; + } + else if (i < contributors[j].n0) + break; + } + + STBIR_ASSERT(total > 0.9f); + STBIR_ASSERT(total < 1.1f); + + scale = 1 / total; + + for (j = 0; j < num_contributors; j++) + { + if (i >= contributors[j].n0 && i <= contributors[j].n1) + *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i - contributors[j].n0) *= scale; + else if (i < contributors[j].n0) + break; + } + } + + // Optimize: Skip zero coefficients and contributions outside of image bounds. + // Do this after normalizing because normalization depends on the n0/n1 values. + for (j = 0; j < num_contributors; j++) + { + int range, max, width; + + skip = 0; + while (*stbir__get_coefficient(coefficients, filter, scale_ratio, j, skip) == 0) + skip++; + + contributors[j].n0 += skip; + + while (contributors[j].n0 < 0) + { + contributors[j].n0++; + skip++; + } + + range = contributors[j].n1 - contributors[j].n0 + 1; + max = stbir__min(num_coefficients, range); + + width = stbir__get_coefficient_width(filter, scale_ratio); + for (i = 0; i < max; i++) + { + if (i + skip >= width) + break; + + *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i) = *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i + skip); + } + + continue; + } + + // Using min to avoid writing into invalid pixels. + for (i = 0; i < num_contributors; i++) + contributors[i].n1 = stbir__min(contributors[i].n1, output_size - 1); +} + +// Each scan line uses the same kernel values so we should calculate the kernel +// values once and then we can use them for every scan line. +static void stbir__calculate_filters(stbir__contributors* contributors, float* coefficients, stbir_filter filter, float scale_ratio, float shift, int input_size, int output_size) +{ + int n; + int total_contributors = stbir__get_contributors(scale_ratio, filter, input_size, output_size); + + if (stbir__use_upsampling(scale_ratio)) + { + float out_pixels_radius = stbir__filter_info_table[filter].support(1 / scale_ratio) * scale_ratio; + + // Looping through out pixels + for (n = 0; n < total_contributors; n++) + { + float in_center_of_out; // Center of the current out pixel in the in pixel space + int in_first_pixel, in_last_pixel; + + stbir__calculate_sample_range_upsample(n, out_pixels_radius, scale_ratio, shift, &in_first_pixel, &in_last_pixel, &in_center_of_out); + + stbir__calculate_coefficients_upsample(filter, scale_ratio, in_first_pixel, in_last_pixel, in_center_of_out, stbir__get_contributor(contributors, n), stbir__get_coefficient(coefficients, filter, scale_ratio, n, 0)); + } + } + else + { + float in_pixels_radius = stbir__filter_info_table[filter].support(scale_ratio) / scale_ratio; + + // Looping through in pixels + for (n = 0; n < total_contributors; n++) + { + float out_center_of_in; // Center of the current out pixel in the in pixel space + int out_first_pixel, out_last_pixel; + int n_adjusted = n - stbir__get_filter_pixel_margin(filter, scale_ratio); + + stbir__calculate_sample_range_downsample(n_adjusted, in_pixels_radius, scale_ratio, shift, &out_first_pixel, &out_last_pixel, &out_center_of_in); + + stbir__calculate_coefficients_downsample(filter, scale_ratio, out_first_pixel, out_last_pixel, out_center_of_in, stbir__get_contributor(contributors, n), stbir__get_coefficient(coefficients, filter, scale_ratio, n, 0)); + } + + stbir__normalize_downsample_coefficients(contributors, coefficients, filter, scale_ratio, input_size, output_size); + } +} + +static float* stbir__get_decode_buffer(stbir__info* stbir_info) +{ + // The 0 index of the decode buffer starts after the margin. This makes + // it okay to use negative indexes on the decode buffer. + return &stbir_info->decode_buffer[stbir_info->horizontal_filter_pixel_margin * stbir_info->channels]; +} + +#define STBIR__DECODE(type, colorspace) ((int)(type) * (STBIR_MAX_COLORSPACES) + (int)(colorspace)) + +static void stbir__decode_scanline(stbir__info* stbir_info, int n) +{ + int c; + int channels = stbir_info->channels; + int alpha_channel = stbir_info->alpha_channel; + int type = stbir_info->type; + int colorspace = stbir_info->colorspace; + int input_w = stbir_info->input_w; + size_t input_stride_bytes = stbir_info->input_stride_bytes; + float* decode_buffer = stbir__get_decode_buffer(stbir_info); + stbir_edge edge_horizontal = stbir_info->edge_horizontal; + stbir_edge edge_vertical = stbir_info->edge_vertical; + size_t in_buffer_row_offset = stbir__edge_wrap(edge_vertical, n, stbir_info->input_h) * input_stride_bytes; + const void* input_data = (char *) stbir_info->input_data + in_buffer_row_offset; + int max_x = input_w + stbir_info->horizontal_filter_pixel_margin; + int decode = STBIR__DECODE(type, colorspace); + + int x = -stbir_info->horizontal_filter_pixel_margin; + + // special handling for STBIR_EDGE_ZERO because it needs to return an item that doesn't appear in the input, + // and we want to avoid paying overhead on every pixel if not STBIR_EDGE_ZERO + if (edge_vertical == STBIR_EDGE_ZERO && (n < 0 || n >= stbir_info->input_h)) + { + for (; x < max_x; x++) + for (c = 0; c < channels; c++) + decode_buffer[x*channels + c] = 0; + return; + } + + switch (decode) + { + case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_LINEAR): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = ((float)((const unsigned char*)input_data)[input_pixel_index + c]) / stbir__max_uint8_as_float; + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_SRGB): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = stbir__srgb_uchar_to_linear_float[((const unsigned char*)input_data)[input_pixel_index + c]]; + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + decode_buffer[decode_pixel_index + alpha_channel] = ((float)((const unsigned char*)input_data)[input_pixel_index + alpha_channel]) / stbir__max_uint8_as_float; + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_LINEAR): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = ((float)((const unsigned short*)input_data)[input_pixel_index + c]) / stbir__max_uint16_as_float; + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_SRGB): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = stbir__srgb_to_linear(((float)((const unsigned short*)input_data)[input_pixel_index + c]) / stbir__max_uint16_as_float); + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + decode_buffer[decode_pixel_index + alpha_channel] = ((float)((const unsigned short*)input_data)[input_pixel_index + alpha_channel]) / stbir__max_uint16_as_float; + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_LINEAR): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = (float)(((double)((const unsigned int*)input_data)[input_pixel_index + c]) / stbir__max_uint32_as_float); + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_SRGB): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = stbir__srgb_to_linear((float)(((double)((const unsigned int*)input_data)[input_pixel_index + c]) / stbir__max_uint32_as_float)); + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + decode_buffer[decode_pixel_index + alpha_channel] = (float)(((double)((const unsigned int*)input_data)[input_pixel_index + alpha_channel]) / stbir__max_uint32_as_float); + } + break; + + case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_LINEAR): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = ((const float*)input_data)[input_pixel_index + c]; + } + break; + + case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_SRGB): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = stbir__srgb_to_linear(((const float*)input_data)[input_pixel_index + c]); + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + decode_buffer[decode_pixel_index + alpha_channel] = ((const float*)input_data)[input_pixel_index + alpha_channel]; + } + + break; + + default: + STBIR_ASSERT(!"Unknown type/colorspace/channels combination."); + break; + } + + if (!(stbir_info->flags & STBIR_FLAG_ALPHA_PREMULTIPLIED)) + { + for (x = -stbir_info->horizontal_filter_pixel_margin; x < max_x; x++) + { + int decode_pixel_index = x * channels; + + // If the alpha value is 0 it will clobber the color values. Make sure it's not. + float alpha = decode_buffer[decode_pixel_index + alpha_channel]; +#ifndef STBIR_NO_ALPHA_EPSILON + if (stbir_info->type != STBIR_TYPE_FLOAT) { + alpha += STBIR_ALPHA_EPSILON; + decode_buffer[decode_pixel_index + alpha_channel] = alpha; + } +#endif + for (c = 0; c < channels; c++) + { + if (c == alpha_channel) + continue; + + decode_buffer[decode_pixel_index + c] *= alpha; + } + } + } + + if (edge_horizontal == STBIR_EDGE_ZERO) + { + for (x = -stbir_info->horizontal_filter_pixel_margin; x < 0; x++) + { + for (c = 0; c < channels; c++) + decode_buffer[x*channels + c] = 0; + } + for (x = input_w; x < max_x; x++) + { + for (c = 0; c < channels; c++) + decode_buffer[x*channels + c] = 0; + } + } +} + +static float* stbir__get_ring_buffer_entry(float* ring_buffer, int index, int ring_buffer_length) +{ + return &ring_buffer[index * ring_buffer_length]; +} + +static float* stbir__add_empty_ring_buffer_entry(stbir__info* stbir_info, int n) +{ + int ring_buffer_index; + float* ring_buffer; + + stbir_info->ring_buffer_last_scanline = n; + + if (stbir_info->ring_buffer_begin_index < 0) + { + ring_buffer_index = stbir_info->ring_buffer_begin_index = 0; + stbir_info->ring_buffer_first_scanline = n; + } + else + { + ring_buffer_index = (stbir_info->ring_buffer_begin_index + (stbir_info->ring_buffer_last_scanline - stbir_info->ring_buffer_first_scanline)) % stbir_info->ring_buffer_num_entries; + STBIR_ASSERT(ring_buffer_index != stbir_info->ring_buffer_begin_index); + } + + ring_buffer = stbir__get_ring_buffer_entry(stbir_info->ring_buffer, ring_buffer_index, stbir_info->ring_buffer_length_bytes / sizeof(float)); + memset(ring_buffer, 0, stbir_info->ring_buffer_length_bytes); + + return ring_buffer; +} + + +static void stbir__resample_horizontal_upsample(stbir__info* stbir_info, float* output_buffer) +{ + int x, k; + int output_w = stbir_info->output_w; + int channels = stbir_info->channels; + float* decode_buffer = stbir__get_decode_buffer(stbir_info); + stbir__contributors* horizontal_contributors = stbir_info->horizontal_contributors; + float* horizontal_coefficients = stbir_info->horizontal_coefficients; + int coefficient_width = stbir_info->horizontal_coefficient_width; + + for (x = 0; x < output_w; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int out_pixel_index = x * channels; + int coefficient_group = coefficient_width * x; + int coefficient_counter = 0; + + STBIR_ASSERT(n1 >= n0); + STBIR_ASSERT(n0 >= -stbir_info->horizontal_filter_pixel_margin); + STBIR_ASSERT(n1 >= -stbir_info->horizontal_filter_pixel_margin); + STBIR_ASSERT(n0 < stbir_info->input_w + stbir_info->horizontal_filter_pixel_margin); + STBIR_ASSERT(n1 < stbir_info->input_w + stbir_info->horizontal_filter_pixel_margin); + + switch (channels) { + case 1: + for (k = n0; k <= n1; k++) + { + int in_pixel_index = k * 1; + float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + } + break; + case 2: + for (k = n0; k <= n1; k++) + { + int in_pixel_index = k * 2; + float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + } + break; + case 3: + for (k = n0; k <= n1; k++) + { + int in_pixel_index = k * 3; + float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient; + } + break; + case 4: + for (k = n0; k <= n1; k++) + { + int in_pixel_index = k * 4; + float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient; + output_buffer[out_pixel_index + 3] += decode_buffer[in_pixel_index + 3] * coefficient; + } + break; + default: + for (k = n0; k <= n1; k++) + { + int in_pixel_index = k * channels; + float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++]; + int c; + STBIR_ASSERT(coefficient != 0); + for (c = 0; c < channels; c++) + output_buffer[out_pixel_index + c] += decode_buffer[in_pixel_index + c] * coefficient; + } + break; + } + } +} + +static void stbir__resample_horizontal_downsample(stbir__info* stbir_info, float* output_buffer) +{ + int x, k; + int input_w = stbir_info->input_w; + int channels = stbir_info->channels; + float* decode_buffer = stbir__get_decode_buffer(stbir_info); + stbir__contributors* horizontal_contributors = stbir_info->horizontal_contributors; + float* horizontal_coefficients = stbir_info->horizontal_coefficients; + int coefficient_width = stbir_info->horizontal_coefficient_width; + int filter_pixel_margin = stbir_info->horizontal_filter_pixel_margin; + int max_x = input_w + filter_pixel_margin * 2; + + STBIR_ASSERT(!stbir__use_width_upsampling(stbir_info)); + + switch (channels) { + case 1: + for (x = 0; x < max_x; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int in_x = x - filter_pixel_margin; + int in_pixel_index = in_x * 1; + int max_n = n1; + int coefficient_group = coefficient_width * x; + + for (k = n0; k <= max_n; k++) + { + int out_pixel_index = k * 1; + float coefficient = horizontal_coefficients[coefficient_group + k - n0]; + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + } + } + break; + + case 2: + for (x = 0; x < max_x; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int in_x = x - filter_pixel_margin; + int in_pixel_index = in_x * 2; + int max_n = n1; + int coefficient_group = coefficient_width * x; + + for (k = n0; k <= max_n; k++) + { + int out_pixel_index = k * 2; + float coefficient = horizontal_coefficients[coefficient_group + k - n0]; + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + } + } + break; + + case 3: + for (x = 0; x < max_x; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int in_x = x - filter_pixel_margin; + int in_pixel_index = in_x * 3; + int max_n = n1; + int coefficient_group = coefficient_width * x; + + for (k = n0; k <= max_n; k++) + { + int out_pixel_index = k * 3; + float coefficient = horizontal_coefficients[coefficient_group + k - n0]; + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient; + } + } + break; + + case 4: + for (x = 0; x < max_x; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int in_x = x - filter_pixel_margin; + int in_pixel_index = in_x * 4; + int max_n = n1; + int coefficient_group = coefficient_width * x; + + for (k = n0; k <= max_n; k++) + { + int out_pixel_index = k * 4; + float coefficient = horizontal_coefficients[coefficient_group + k - n0]; + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient; + output_buffer[out_pixel_index + 3] += decode_buffer[in_pixel_index + 3] * coefficient; + } + } + break; + + default: + for (x = 0; x < max_x; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int in_x = x - filter_pixel_margin; + int in_pixel_index = in_x * channels; + int max_n = n1; + int coefficient_group = coefficient_width * x; + + for (k = n0; k <= max_n; k++) + { + int c; + int out_pixel_index = k * channels; + float coefficient = horizontal_coefficients[coefficient_group + k - n0]; + for (c = 0; c < channels; c++) + output_buffer[out_pixel_index + c] += decode_buffer[in_pixel_index + c] * coefficient; + } + } + break; + } +} + +static void stbir__decode_and_resample_upsample(stbir__info* stbir_info, int n) +{ + // Decode the nth scanline from the source image into the decode buffer. + stbir__decode_scanline(stbir_info, n); + + // Now resample it into the ring buffer. + if (stbir__use_width_upsampling(stbir_info)) + stbir__resample_horizontal_upsample(stbir_info, stbir__add_empty_ring_buffer_entry(stbir_info, n)); + else + stbir__resample_horizontal_downsample(stbir_info, stbir__add_empty_ring_buffer_entry(stbir_info, n)); + + // Now it's sitting in the ring buffer ready to be used as source for the vertical sampling. +} + +static void stbir__decode_and_resample_downsample(stbir__info* stbir_info, int n) +{ + // Decode the nth scanline from the source image into the decode buffer. + stbir__decode_scanline(stbir_info, n); + + memset(stbir_info->horizontal_buffer, 0, stbir_info->output_w * stbir_info->channels * sizeof(float)); + + // Now resample it into the horizontal buffer. + if (stbir__use_width_upsampling(stbir_info)) + stbir__resample_horizontal_upsample(stbir_info, stbir_info->horizontal_buffer); + else + stbir__resample_horizontal_downsample(stbir_info, stbir_info->horizontal_buffer); + + // Now it's sitting in the horizontal buffer ready to be distributed into the ring buffers. +} + +// Get the specified scan line from the ring buffer. +static float* stbir__get_ring_buffer_scanline(int get_scanline, float* ring_buffer, int begin_index, int first_scanline, int ring_buffer_num_entries, int ring_buffer_length) +{ + int ring_buffer_index = (begin_index + (get_scanline - first_scanline)) % ring_buffer_num_entries; + return stbir__get_ring_buffer_entry(ring_buffer, ring_buffer_index, ring_buffer_length); +} + + +static void stbir__encode_scanline(stbir__info* stbir_info, int num_pixels, void *output_buffer, float *encode_buffer, int channels, int alpha_channel, int decode) +{ + int x; + int n; + int num_nonalpha; + stbir_uint16 nonalpha[STBIR_MAX_CHANNELS]; + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_PREMULTIPLIED)) + { + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + float alpha = encode_buffer[pixel_index + alpha_channel]; + float reciprocal_alpha = alpha ? 1.0f / alpha : 0; + + // unrolling this produced a 1% slowdown upscaling a large RGBA linear-space image on my machine - stb + for (n = 0; n < channels; n++) + if (n != alpha_channel) + encode_buffer[pixel_index + n] *= reciprocal_alpha; + + // We added in a small epsilon to prevent the color channel from being deleted with zero alpha. + // Because we only add it for integer types, it will automatically be discarded on integer + // conversion, so we don't need to subtract it back out (which would be problematic for + // numeric precision reasons). + } + } + + // build a table of all channels that need colorspace correction, so + // we don't perform colorspace correction on channels that don't need it. + for (x = 0, num_nonalpha = 0; x < channels; ++x) + { + if (x != alpha_channel || (stbir_info->flags & STBIR_FLAG_ALPHA_USES_COLORSPACE)) + { + nonalpha[num_nonalpha++] = (stbir_uint16)x; + } + } + + #define STBIR__ROUND_INT(f) ((int) ((f)+0.5)) + #define STBIR__ROUND_UINT(f) ((stbir_uint32) ((f)+0.5)) + + #ifdef STBIR__SATURATE_INT + #define STBIR__ENCODE_LINEAR8(f) stbir__saturate8 (STBIR__ROUND_INT((f) * stbir__max_uint8_as_float )) + #define STBIR__ENCODE_LINEAR16(f) stbir__saturate16(STBIR__ROUND_INT((f) * stbir__max_uint16_as_float)) + #else + #define STBIR__ENCODE_LINEAR8(f) (unsigned char ) STBIR__ROUND_INT(stbir__saturate(f) * stbir__max_uint8_as_float ) + #define STBIR__ENCODE_LINEAR16(f) (unsigned short) STBIR__ROUND_INT(stbir__saturate(f) * stbir__max_uint16_as_float) + #endif + + switch (decode) + { + case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_LINEAR): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < channels; n++) + { + int index = pixel_index + n; + ((unsigned char*)output_buffer)[index] = STBIR__ENCODE_LINEAR8(encode_buffer[index]); + } + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_SRGB): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < num_nonalpha; n++) + { + int index = pixel_index + nonalpha[n]; + ((unsigned char*)output_buffer)[index] = stbir__linear_to_srgb_uchar(encode_buffer[index]); + } + + if (!(stbir_info->flags & STBIR_FLAG_ALPHA_USES_COLORSPACE)) + ((unsigned char *)output_buffer)[pixel_index + alpha_channel] = STBIR__ENCODE_LINEAR8(encode_buffer[pixel_index+alpha_channel]); + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_LINEAR): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < channels; n++) + { + int index = pixel_index + n; + ((unsigned short*)output_buffer)[index] = STBIR__ENCODE_LINEAR16(encode_buffer[index]); + } + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_SRGB): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < num_nonalpha; n++) + { + int index = pixel_index + nonalpha[n]; + ((unsigned short*)output_buffer)[index] = (unsigned short)STBIR__ROUND_INT(stbir__linear_to_srgb(stbir__saturate(encode_buffer[index])) * stbir__max_uint16_as_float); + } + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + ((unsigned short*)output_buffer)[pixel_index + alpha_channel] = STBIR__ENCODE_LINEAR16(encode_buffer[pixel_index + alpha_channel]); + } + + break; + + case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_LINEAR): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < channels; n++) + { + int index = pixel_index + n; + ((unsigned int*)output_buffer)[index] = (unsigned int)STBIR__ROUND_UINT(((double)stbir__saturate(encode_buffer[index])) * stbir__max_uint32_as_float); + } + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_SRGB): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < num_nonalpha; n++) + { + int index = pixel_index + nonalpha[n]; + ((unsigned int*)output_buffer)[index] = (unsigned int)STBIR__ROUND_UINT(((double)stbir__linear_to_srgb(stbir__saturate(encode_buffer[index]))) * stbir__max_uint32_as_float); + } + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + ((unsigned int*)output_buffer)[pixel_index + alpha_channel] = (unsigned int)STBIR__ROUND_INT(((double)stbir__saturate(encode_buffer[pixel_index + alpha_channel])) * stbir__max_uint32_as_float); + } + break; + + case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_LINEAR): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < channels; n++) + { + int index = pixel_index + n; + ((float*)output_buffer)[index] = encode_buffer[index]; + } + } + break; + + case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_SRGB): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < num_nonalpha; n++) + { + int index = pixel_index + nonalpha[n]; + ((float*)output_buffer)[index] = stbir__linear_to_srgb(encode_buffer[index]); + } + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + ((float*)output_buffer)[pixel_index + alpha_channel] = encode_buffer[pixel_index + alpha_channel]; + } + break; + + default: + STBIR_ASSERT(!"Unknown type/colorspace/channels combination."); + break; + } +} + +static void stbir__resample_vertical_upsample(stbir__info* stbir_info, int n) +{ + int x, k; + int output_w = stbir_info->output_w; + stbir__contributors* vertical_contributors = stbir_info->vertical_contributors; + float* vertical_coefficients = stbir_info->vertical_coefficients; + int channels = stbir_info->channels; + int alpha_channel = stbir_info->alpha_channel; + int type = stbir_info->type; + int colorspace = stbir_info->colorspace; + int ring_buffer_entries = stbir_info->ring_buffer_num_entries; + void* output_data = stbir_info->output_data; + float* encode_buffer = stbir_info->encode_buffer; + int decode = STBIR__DECODE(type, colorspace); + int coefficient_width = stbir_info->vertical_coefficient_width; + int coefficient_counter; + int contributor = n; + + float* ring_buffer = stbir_info->ring_buffer; + int ring_buffer_begin_index = stbir_info->ring_buffer_begin_index; + int ring_buffer_first_scanline = stbir_info->ring_buffer_first_scanline; + int ring_buffer_length = stbir_info->ring_buffer_length_bytes/sizeof(float); + + int n0,n1, output_row_start; + int coefficient_group = coefficient_width * contributor; + + n0 = vertical_contributors[contributor].n0; + n1 = vertical_contributors[contributor].n1; + + output_row_start = n * stbir_info->output_stride_bytes; + + STBIR_ASSERT(stbir__use_height_upsampling(stbir_info)); + + memset(encode_buffer, 0, output_w * sizeof(float) * channels); + + // I tried reblocking this for better cache usage of encode_buffer + // (using x_outer, k, x_inner), but it lost speed. -- stb + + coefficient_counter = 0; + switch (channels) { + case 1: + for (k = n0; k <= n1; k++) + { + int coefficient_index = coefficient_counter++; + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + for (x = 0; x < output_w; ++x) + { + int in_pixel_index = x * 1; + encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient; + } + } + break; + case 2: + for (k = n0; k <= n1; k++) + { + int coefficient_index = coefficient_counter++; + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + for (x = 0; x < output_w; ++x) + { + int in_pixel_index = x * 2; + encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient; + encode_buffer[in_pixel_index + 1] += ring_buffer_entry[in_pixel_index + 1] * coefficient; + } + } + break; + case 3: + for (k = n0; k <= n1; k++) + { + int coefficient_index = coefficient_counter++; + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + for (x = 0; x < output_w; ++x) + { + int in_pixel_index = x * 3; + encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient; + encode_buffer[in_pixel_index + 1] += ring_buffer_entry[in_pixel_index + 1] * coefficient; + encode_buffer[in_pixel_index + 2] += ring_buffer_entry[in_pixel_index + 2] * coefficient; + } + } + break; + case 4: + for (k = n0; k <= n1; k++) + { + int coefficient_index = coefficient_counter++; + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + for (x = 0; x < output_w; ++x) + { + int in_pixel_index = x * 4; + encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient; + encode_buffer[in_pixel_index + 1] += ring_buffer_entry[in_pixel_index + 1] * coefficient; + encode_buffer[in_pixel_index + 2] += ring_buffer_entry[in_pixel_index + 2] * coefficient; + encode_buffer[in_pixel_index + 3] += ring_buffer_entry[in_pixel_index + 3] * coefficient; + } + } + break; + default: + for (k = n0; k <= n1; k++) + { + int coefficient_index = coefficient_counter++; + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + for (x = 0; x < output_w; ++x) + { + int in_pixel_index = x * channels; + int c; + for (c = 0; c < channels; c++) + encode_buffer[in_pixel_index + c] += ring_buffer_entry[in_pixel_index + c] * coefficient; + } + } + break; + } + stbir__encode_scanline(stbir_info, output_w, (char *) output_data + output_row_start, encode_buffer, channels, alpha_channel, decode); +} + +static void stbir__resample_vertical_downsample(stbir__info* stbir_info, int n) +{ + int x, k; + int output_w = stbir_info->output_w; + stbir__contributors* vertical_contributors = stbir_info->vertical_contributors; + float* vertical_coefficients = stbir_info->vertical_coefficients; + int channels = stbir_info->channels; + int ring_buffer_entries = stbir_info->ring_buffer_num_entries; + float* horizontal_buffer = stbir_info->horizontal_buffer; + int coefficient_width = stbir_info->vertical_coefficient_width; + int contributor = n + stbir_info->vertical_filter_pixel_margin; + + float* ring_buffer = stbir_info->ring_buffer; + int ring_buffer_begin_index = stbir_info->ring_buffer_begin_index; + int ring_buffer_first_scanline = stbir_info->ring_buffer_first_scanline; + int ring_buffer_length = stbir_info->ring_buffer_length_bytes/sizeof(float); + int n0,n1; + + n0 = vertical_contributors[contributor].n0; + n1 = vertical_contributors[contributor].n1; + + STBIR_ASSERT(!stbir__use_height_upsampling(stbir_info)); + + for (k = n0; k <= n1; k++) + { + int coefficient_index = k - n0; + int coefficient_group = coefficient_width * contributor; + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + + switch (channels) { + case 1: + for (x = 0; x < output_w; x++) + { + int in_pixel_index = x * 1; + ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient; + } + break; + case 2: + for (x = 0; x < output_w; x++) + { + int in_pixel_index = x * 2; + ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient; + ring_buffer_entry[in_pixel_index + 1] += horizontal_buffer[in_pixel_index + 1] * coefficient; + } + break; + case 3: + for (x = 0; x < output_w; x++) + { + int in_pixel_index = x * 3; + ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient; + ring_buffer_entry[in_pixel_index + 1] += horizontal_buffer[in_pixel_index + 1] * coefficient; + ring_buffer_entry[in_pixel_index + 2] += horizontal_buffer[in_pixel_index + 2] * coefficient; + } + break; + case 4: + for (x = 0; x < output_w; x++) + { + int in_pixel_index = x * 4; + ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient; + ring_buffer_entry[in_pixel_index + 1] += horizontal_buffer[in_pixel_index + 1] * coefficient; + ring_buffer_entry[in_pixel_index + 2] += horizontal_buffer[in_pixel_index + 2] * coefficient; + ring_buffer_entry[in_pixel_index + 3] += horizontal_buffer[in_pixel_index + 3] * coefficient; + } + break; + default: + for (x = 0; x < output_w; x++) + { + int in_pixel_index = x * channels; + + int c; + for (c = 0; c < channels; c++) + ring_buffer_entry[in_pixel_index + c] += horizontal_buffer[in_pixel_index + c] * coefficient; + } + break; + } + } +} + +static void stbir__buffer_loop_upsample(stbir__info* stbir_info) +{ + int y; + float scale_ratio = stbir_info->vertical_scale; + float out_scanlines_radius = stbir__filter_info_table[stbir_info->vertical_filter].support(1/scale_ratio) * scale_ratio; + + STBIR_ASSERT(stbir__use_height_upsampling(stbir_info)); + + for (y = 0; y < stbir_info->output_h; y++) + { + float in_center_of_out = 0; // Center of the current out scanline in the in scanline space + int in_first_scanline = 0, in_last_scanline = 0; + + stbir__calculate_sample_range_upsample(y, out_scanlines_radius, scale_ratio, stbir_info->vertical_shift, &in_first_scanline, &in_last_scanline, &in_center_of_out); + + STBIR_ASSERT(in_last_scanline - in_first_scanline + 1 <= stbir_info->ring_buffer_num_entries); + + if (stbir_info->ring_buffer_begin_index >= 0) + { + // Get rid of whatever we don't need anymore. + while (in_first_scanline > stbir_info->ring_buffer_first_scanline) + { + if (stbir_info->ring_buffer_first_scanline == stbir_info->ring_buffer_last_scanline) + { + // We just popped the last scanline off the ring buffer. + // Reset it to the empty state. + stbir_info->ring_buffer_begin_index = -1; + stbir_info->ring_buffer_first_scanline = 0; + stbir_info->ring_buffer_last_scanline = 0; + break; + } + else + { + stbir_info->ring_buffer_first_scanline++; + stbir_info->ring_buffer_begin_index = (stbir_info->ring_buffer_begin_index + 1) % stbir_info->ring_buffer_num_entries; + } + } + } + + // Load in new ones. + if (stbir_info->ring_buffer_begin_index < 0) + stbir__decode_and_resample_upsample(stbir_info, in_first_scanline); + + while (in_last_scanline > stbir_info->ring_buffer_last_scanline) + stbir__decode_and_resample_upsample(stbir_info, stbir_info->ring_buffer_last_scanline + 1); + + // Now all buffers should be ready to write a row of vertical sampling. + stbir__resample_vertical_upsample(stbir_info, y); + + STBIR_PROGRESS_REPORT((float)y / stbir_info->output_h); + } +} + +static void stbir__empty_ring_buffer(stbir__info* stbir_info, int first_necessary_scanline) +{ + int output_stride_bytes = stbir_info->output_stride_bytes; + int channels = stbir_info->channels; + int alpha_channel = stbir_info->alpha_channel; + int type = stbir_info->type; + int colorspace = stbir_info->colorspace; + int output_w = stbir_info->output_w; + void* output_data = stbir_info->output_data; + int decode = STBIR__DECODE(type, colorspace); + + float* ring_buffer = stbir_info->ring_buffer; + int ring_buffer_length = stbir_info->ring_buffer_length_bytes/sizeof(float); + + if (stbir_info->ring_buffer_begin_index >= 0) + { + // Get rid of whatever we don't need anymore. + while (first_necessary_scanline > stbir_info->ring_buffer_first_scanline) + { + if (stbir_info->ring_buffer_first_scanline >= 0 && stbir_info->ring_buffer_first_scanline < stbir_info->output_h) + { + int output_row_start = stbir_info->ring_buffer_first_scanline * output_stride_bytes; + float* ring_buffer_entry = stbir__get_ring_buffer_entry(ring_buffer, stbir_info->ring_buffer_begin_index, ring_buffer_length); + stbir__encode_scanline(stbir_info, output_w, (char *) output_data + output_row_start, ring_buffer_entry, channels, alpha_channel, decode); + STBIR_PROGRESS_REPORT((float)stbir_info->ring_buffer_first_scanline / stbir_info->output_h); + } + + if (stbir_info->ring_buffer_first_scanline == stbir_info->ring_buffer_last_scanline) + { + // We just popped the last scanline off the ring buffer. + // Reset it to the empty state. + stbir_info->ring_buffer_begin_index = -1; + stbir_info->ring_buffer_first_scanline = 0; + stbir_info->ring_buffer_last_scanline = 0; + break; + } + else + { + stbir_info->ring_buffer_first_scanline++; + stbir_info->ring_buffer_begin_index = (stbir_info->ring_buffer_begin_index + 1) % stbir_info->ring_buffer_num_entries; + } + } + } +} + +static void stbir__buffer_loop_downsample(stbir__info* stbir_info) +{ + int y; + float scale_ratio = stbir_info->vertical_scale; + int output_h = stbir_info->output_h; + float in_pixels_radius = stbir__filter_info_table[stbir_info->vertical_filter].support(scale_ratio) / scale_ratio; + int pixel_margin = stbir_info->vertical_filter_pixel_margin; + int max_y = stbir_info->input_h + pixel_margin; + + STBIR_ASSERT(!stbir__use_height_upsampling(stbir_info)); + + for (y = -pixel_margin; y < max_y; y++) + { + float out_center_of_in; // Center of the current out scanline in the in scanline space + int out_first_scanline, out_last_scanline; + + stbir__calculate_sample_range_downsample(y, in_pixels_radius, scale_ratio, stbir_info->vertical_shift, &out_first_scanline, &out_last_scanline, &out_center_of_in); + + STBIR_ASSERT(out_last_scanline - out_first_scanline + 1 <= stbir_info->ring_buffer_num_entries); + + if (out_last_scanline < 0 || out_first_scanline >= output_h) + continue; + + stbir__empty_ring_buffer(stbir_info, out_first_scanline); + + stbir__decode_and_resample_downsample(stbir_info, y); + + // Load in new ones. + if (stbir_info->ring_buffer_begin_index < 0) + stbir__add_empty_ring_buffer_entry(stbir_info, out_first_scanline); + + while (out_last_scanline > stbir_info->ring_buffer_last_scanline) + stbir__add_empty_ring_buffer_entry(stbir_info, stbir_info->ring_buffer_last_scanline + 1); + + // Now the horizontal buffer is ready to write to all ring buffer rows. + stbir__resample_vertical_downsample(stbir_info, y); + } + + stbir__empty_ring_buffer(stbir_info, stbir_info->output_h); +} + +static void stbir__setup(stbir__info *info, int input_w, int input_h, int output_w, int output_h, int channels) +{ + info->input_w = input_w; + info->input_h = input_h; + info->output_w = output_w; + info->output_h = output_h; + info->channels = channels; +} + +static void stbir__calculate_transform(stbir__info *info, float s0, float t0, float s1, float t1, float *transform) +{ + info->s0 = s0; + info->t0 = t0; + info->s1 = s1; + info->t1 = t1; + + if (transform) + { + info->horizontal_scale = transform[0]; + info->vertical_scale = transform[1]; + info->horizontal_shift = transform[2]; + info->vertical_shift = transform[3]; + } + else + { + info->horizontal_scale = ((float)info->output_w / info->input_w) / (s1 - s0); + info->vertical_scale = ((float)info->output_h / info->input_h) / (t1 - t0); + + info->horizontal_shift = s0 * info->output_w / (s1 - s0); + info->vertical_shift = t0 * info->output_h / (t1 - t0); + } +} + +static void stbir__choose_filter(stbir__info *info, stbir_filter h_filter, stbir_filter v_filter) +{ + if (h_filter == 0) + h_filter = stbir__use_upsampling(info->horizontal_scale) ? STBIR_DEFAULT_FILTER_UPSAMPLE : STBIR_DEFAULT_FILTER_DOWNSAMPLE; + if (v_filter == 0) + v_filter = stbir__use_upsampling(info->vertical_scale) ? STBIR_DEFAULT_FILTER_UPSAMPLE : STBIR_DEFAULT_FILTER_DOWNSAMPLE; + info->horizontal_filter = h_filter; + info->vertical_filter = v_filter; +} + +static stbir_uint32 stbir__calculate_memory(stbir__info *info) +{ + int pixel_margin = stbir__get_filter_pixel_margin(info->horizontal_filter, info->horizontal_scale); + int filter_height = stbir__get_filter_pixel_width(info->vertical_filter, info->vertical_scale); + + info->horizontal_num_contributors = stbir__get_contributors(info->horizontal_scale, info->horizontal_filter, info->input_w, info->output_w); + info->vertical_num_contributors = stbir__get_contributors(info->vertical_scale , info->vertical_filter , info->input_h, info->output_h); + + // One extra entry because floating point precision problems sometimes cause an extra to be necessary. + info->ring_buffer_num_entries = filter_height + 1; + + info->horizontal_contributors_size = info->horizontal_num_contributors * sizeof(stbir__contributors); + info->horizontal_coefficients_size = stbir__get_total_horizontal_coefficients(info) * sizeof(float); + info->vertical_contributors_size = info->vertical_num_contributors * sizeof(stbir__contributors); + info->vertical_coefficients_size = stbir__get_total_vertical_coefficients(info) * sizeof(float); + info->decode_buffer_size = (info->input_w + pixel_margin * 2) * info->channels * sizeof(float); + info->horizontal_buffer_size = info->output_w * info->channels * sizeof(float); + info->ring_buffer_size = info->output_w * info->channels * info->ring_buffer_num_entries * sizeof(float); + info->encode_buffer_size = info->output_w * info->channels * sizeof(float); + + STBIR_ASSERT(info->horizontal_filter != 0); + STBIR_ASSERT(info->horizontal_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); // this now happens too late + STBIR_ASSERT(info->vertical_filter != 0); + STBIR_ASSERT(info->vertical_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); // this now happens too late + + if (stbir__use_height_upsampling(info)) + // The horizontal buffer is for when we're downsampling the height and we + // can't output the result of sampling the decode buffer directly into the + // ring buffers. + info->horizontal_buffer_size = 0; + else + // The encode buffer is to retain precision in the height upsampling method + // and isn't used when height downsampling. + info->encode_buffer_size = 0; + + return info->horizontal_contributors_size + info->horizontal_coefficients_size + + info->vertical_contributors_size + info->vertical_coefficients_size + + info->decode_buffer_size + info->horizontal_buffer_size + + info->ring_buffer_size + info->encode_buffer_size; +} + +static int stbir__resize_allocated(stbir__info *info, + const void* input_data, int input_stride_in_bytes, + void* output_data, int output_stride_in_bytes, + int alpha_channel, stbir_uint32 flags, stbir_datatype type, + stbir_edge edge_horizontal, stbir_edge edge_vertical, stbir_colorspace colorspace, + void* tempmem, size_t tempmem_size_in_bytes) +{ + size_t memory_required = stbir__calculate_memory(info); + + int width_stride_input = input_stride_in_bytes ? input_stride_in_bytes : info->channels * info->input_w * stbir__type_size[type]; + int width_stride_output = output_stride_in_bytes ? output_stride_in_bytes : info->channels * info->output_w * stbir__type_size[type]; + +#ifdef STBIR_DEBUG_OVERWRITE_TEST +#define OVERWRITE_ARRAY_SIZE 8 + unsigned char overwrite_output_before_pre[OVERWRITE_ARRAY_SIZE]; + unsigned char overwrite_tempmem_before_pre[OVERWRITE_ARRAY_SIZE]; + unsigned char overwrite_output_after_pre[OVERWRITE_ARRAY_SIZE]; + unsigned char overwrite_tempmem_after_pre[OVERWRITE_ARRAY_SIZE]; + + size_t begin_forbidden = width_stride_output * (info->output_h - 1) + info->output_w * info->channels * stbir__type_size[type]; + memcpy(overwrite_output_before_pre, &((unsigned char*)output_data)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE); + memcpy(overwrite_output_after_pre, &((unsigned char*)output_data)[begin_forbidden], OVERWRITE_ARRAY_SIZE); + memcpy(overwrite_tempmem_before_pre, &((unsigned char*)tempmem)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE); + memcpy(overwrite_tempmem_after_pre, &((unsigned char*)tempmem)[tempmem_size_in_bytes], OVERWRITE_ARRAY_SIZE); +#endif + + STBIR_ASSERT(info->channels >= 0); + STBIR_ASSERT(info->channels <= STBIR_MAX_CHANNELS); + + if (info->channels < 0 || info->channels > STBIR_MAX_CHANNELS) + return 0; + + STBIR_ASSERT(info->horizontal_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); + STBIR_ASSERT(info->vertical_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); + + if (info->horizontal_filter >= STBIR__ARRAY_SIZE(stbir__filter_info_table)) + return 0; + if (info->vertical_filter >= STBIR__ARRAY_SIZE(stbir__filter_info_table)) + return 0; + + if (alpha_channel < 0) + flags |= STBIR_FLAG_ALPHA_USES_COLORSPACE | STBIR_FLAG_ALPHA_PREMULTIPLIED; + + if (!(flags&STBIR_FLAG_ALPHA_USES_COLORSPACE) || !(flags&STBIR_FLAG_ALPHA_PREMULTIPLIED)) { + STBIR_ASSERT(alpha_channel >= 0 && alpha_channel < info->channels); + } + + if (alpha_channel >= info->channels) + return 0; + + STBIR_ASSERT(tempmem); + + if (!tempmem) + return 0; + + STBIR_ASSERT(tempmem_size_in_bytes >= memory_required); + + if (tempmem_size_in_bytes < memory_required) + return 0; + + memset(tempmem, 0, tempmem_size_in_bytes); + + info->input_data = input_data; + info->input_stride_bytes = width_stride_input; + + info->output_data = output_data; + info->output_stride_bytes = width_stride_output; + + info->alpha_channel = alpha_channel; + info->flags = flags; + info->type = type; + info->edge_horizontal = edge_horizontal; + info->edge_vertical = edge_vertical; + info->colorspace = colorspace; + + info->horizontal_coefficient_width = stbir__get_coefficient_width (info->horizontal_filter, info->horizontal_scale); + info->vertical_coefficient_width = stbir__get_coefficient_width (info->vertical_filter , info->vertical_scale ); + info->horizontal_filter_pixel_width = stbir__get_filter_pixel_width (info->horizontal_filter, info->horizontal_scale); + info->vertical_filter_pixel_width = stbir__get_filter_pixel_width (info->vertical_filter , info->vertical_scale ); + info->horizontal_filter_pixel_margin = stbir__get_filter_pixel_margin(info->horizontal_filter, info->horizontal_scale); + info->vertical_filter_pixel_margin = stbir__get_filter_pixel_margin(info->vertical_filter , info->vertical_scale ); + + info->ring_buffer_length_bytes = info->output_w * info->channels * sizeof(float); + info->decode_buffer_pixels = info->input_w + info->horizontal_filter_pixel_margin * 2; + +#define STBIR__NEXT_MEMPTR(current, newtype) (newtype*)(((unsigned char*)current) + current##_size) + + info->horizontal_contributors = (stbir__contributors *) tempmem; + info->horizontal_coefficients = STBIR__NEXT_MEMPTR(info->horizontal_contributors, float); + info->vertical_contributors = STBIR__NEXT_MEMPTR(info->horizontal_coefficients, stbir__contributors); + info->vertical_coefficients = STBIR__NEXT_MEMPTR(info->vertical_contributors, float); + info->decode_buffer = STBIR__NEXT_MEMPTR(info->vertical_coefficients, float); + + if (stbir__use_height_upsampling(info)) + { + info->horizontal_buffer = NULL; + info->ring_buffer = STBIR__NEXT_MEMPTR(info->decode_buffer, float); + info->encode_buffer = STBIR__NEXT_MEMPTR(info->ring_buffer, float); + + STBIR_ASSERT((size_t)STBIR__NEXT_MEMPTR(info->encode_buffer, unsigned char) == (size_t)tempmem + tempmem_size_in_bytes); + } + else + { + info->horizontal_buffer = STBIR__NEXT_MEMPTR(info->decode_buffer, float); + info->ring_buffer = STBIR__NEXT_MEMPTR(info->horizontal_buffer, float); + info->encode_buffer = NULL; + + STBIR_ASSERT((size_t)STBIR__NEXT_MEMPTR(info->ring_buffer, unsigned char) == (size_t)tempmem + tempmem_size_in_bytes); + } + +#undef STBIR__NEXT_MEMPTR + + // This signals that the ring buffer is empty + info->ring_buffer_begin_index = -1; + + stbir__calculate_filters(info->horizontal_contributors, info->horizontal_coefficients, info->horizontal_filter, info->horizontal_scale, info->horizontal_shift, info->input_w, info->output_w); + stbir__calculate_filters(info->vertical_contributors, info->vertical_coefficients, info->vertical_filter, info->vertical_scale, info->vertical_shift, info->input_h, info->output_h); + + STBIR_PROGRESS_REPORT(0); + + if (stbir__use_height_upsampling(info)) + stbir__buffer_loop_upsample(info); + else + stbir__buffer_loop_downsample(info); + + STBIR_PROGRESS_REPORT(1); + +#ifdef STBIR_DEBUG_OVERWRITE_TEST + STBIR_ASSERT(memcmp(overwrite_output_before_pre, &((unsigned char*)output_data)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE) == 0); + STBIR_ASSERT(memcmp(overwrite_output_after_pre, &((unsigned char*)output_data)[begin_forbidden], OVERWRITE_ARRAY_SIZE) == 0); + STBIR_ASSERT(memcmp(overwrite_tempmem_before_pre, &((unsigned char*)tempmem)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE) == 0); + STBIR_ASSERT(memcmp(overwrite_tempmem_after_pre, &((unsigned char*)tempmem)[tempmem_size_in_bytes], OVERWRITE_ARRAY_SIZE) == 0); +#endif + + return 1; +} + + +static int stbir__resize_arbitrary( + void *alloc_context, + const void* input_data, int input_w, int input_h, int input_stride_in_bytes, + void* output_data, int output_w, int output_h, int output_stride_in_bytes, + float s0, float t0, float s1, float t1, float *transform, + int channels, int alpha_channel, stbir_uint32 flags, stbir_datatype type, + stbir_filter h_filter, stbir_filter v_filter, + stbir_edge edge_horizontal, stbir_edge edge_vertical, stbir_colorspace colorspace) +{ + stbir__info info; + int result; + size_t memory_required; + void* extra_memory; + + stbir__setup(&info, input_w, input_h, output_w, output_h, channels); + stbir__calculate_transform(&info, s0,t0,s1,t1,transform); + stbir__choose_filter(&info, h_filter, v_filter); + memory_required = stbir__calculate_memory(&info); + extra_memory = STBIR_MALLOC(memory_required, alloc_context); + + if (!extra_memory) + return 0; + + result = stbir__resize_allocated(&info, input_data, input_stride_in_bytes, + output_data, output_stride_in_bytes, + alpha_channel, flags, type, + edge_horizontal, edge_vertical, + colorspace, extra_memory, memory_required); + + STBIR_FREE(extra_memory, alloc_context); + + return result; +} + +STBIRDEF int stbir_resize_uint8( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels) +{ + return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,-1,0, STBIR_TYPE_UINT8, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT, + STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_LINEAR); +} + +STBIRDEF int stbir_resize_float( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + float *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels) +{ + return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,-1,0, STBIR_TYPE_FLOAT, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT, + STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_LINEAR); +} + +STBIRDEF int stbir_resize_uint8_srgb(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags) +{ + return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT8, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT, + STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_SRGB); +} + +STBIRDEF int stbir_resize_uint8_srgb_edgemode(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode) +{ + return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT8, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT, + edge_wrap_mode, edge_wrap_mode, STBIR_COLORSPACE_SRGB); +} + +STBIRDEF int stbir_resize_uint8_generic( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context) +{ + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT8, filter, filter, + edge_wrap_mode, edge_wrap_mode, space); +} + +STBIRDEF int stbir_resize_uint16_generic(const stbir_uint16 *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + stbir_uint16 *output_pixels , int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context) +{ + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT16, filter, filter, + edge_wrap_mode, edge_wrap_mode, space); +} + + +STBIRDEF int stbir_resize_float_generic( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + float *output_pixels , int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context) +{ + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_FLOAT, filter, filter, + edge_wrap_mode, edge_wrap_mode, space); +} + + +STBIRDEF int stbir_resize( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context) +{ + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, datatype, filter_horizontal, filter_vertical, + edge_mode_horizontal, edge_mode_vertical, space); +} + + +STBIRDEF int stbir_resize_subpixel(const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context, + float x_scale, float y_scale, + float x_offset, float y_offset) +{ + float transform[4]; + transform[0] = x_scale; + transform[1] = y_scale; + transform[2] = x_offset; + transform[3] = y_offset; + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,transform,num_channels,alpha_channel,flags, datatype, filter_horizontal, filter_vertical, + edge_mode_horizontal, edge_mode_vertical, space); +} + +STBIRDEF int stbir_resize_region( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context, + float s0, float t0, float s1, float t1) +{ + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + s0,t0,s1,t1,NULL,num_channels,alpha_channel,flags, datatype, filter_horizontal, filter_vertical, + edge_mode_horizontal, edge_mode_vertical, space); +} + +#endif // STB_IMAGE_RESIZE_IMPLEMENTATION + +/* +------------------------------------------------------------------------------ +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/gameSource/transitionBank.cpp b/gameSource/transitionBank.cpp index 263ed65eb..f67d66558 100644 --- a/gameSource/transitionBank.cpp +++ b/gameSource/transitionBank.cpp @@ -867,8 +867,17 @@ void initTransBankFinish() { newTrans.reverseUseTarget = false; newTrans.noUseActor = false; newTrans.noUseTarget = false; - newTrans.actorMinUseFraction = 0.0f; - newTrans.targetMinUseFraction = 0.0f; + // these values are only used when setting up use-dummy + // transitions (to tell us where the cut-off might be, + // when an object is too used to have a transition) + // In old code, we set these to 0.0 for all dummy transitions. + // However, our tool tip logic now uses this (to show a (FRESH) + // or (FULL) tag to show limits of which objects can be used + // in a transition. + // Thus, it's fine if these values "ride along" in + // dummy transitions. + newTrans.actorMinUseFraction = tr->actorMinUseFraction; + newTrans.targetMinUseFraction = tr->targetMinUseFraction; char processed = false; @@ -1093,7 +1102,8 @@ void initTransBankFinish() { if( target != NULL && newActor != NULL && target->numUses > 1 && - target->numUses == newActor->numUses ) { + target->numUses == newActor->numUses && + target->useChance == newActor->useChance ) { // use preservation between target and new actor // generate one for each use dummy @@ -1108,7 +1118,8 @@ void initTransBankFinish() { else if( actor != NULL && newTarget != NULL && actor->numUses > 1 && - actor->numUses == newTarget->numUses ) { + actor->numUses == newTarget->numUses && + actor->useChance == newTarget->useChance ) { // use preservation between actor and new target // generate one for each use dummy diff --git a/server/curseDB.cpp b/server/curseDB.cpp index e610577ea..f561ab75a 100644 --- a/server/curseDB.cpp +++ b/server/curseDB.cpp @@ -6,6 +6,8 @@ #include "lineardb3.h" #include "dbCommon.h" +#include "curseLog.h" + #include "minorGems/util/SettingsManager.h" #include "minorGems/util/log/AppLog.h" @@ -137,93 +139,89 @@ static char cullStale() { +// inIndex = 0 for sender, 1 for receiver +// result NOT destroyed by caller (pointer to internal buffer) + +char senderBuffer[40]; +char receiverBuffer[40]; + +static char *getEmailFromKey( unsigned char *inKey, int inIndex ) { + + sscanf( (char*)inKey, "%39[^,],%39s", senderBuffer, receiverBuffer ); + + if( inIndex == 0 ) { + return senderBuffer; + } + else { + return receiverBuffer; + } + } + + + + +void incrementCurseCount( const char *inReceiverEmail ); + + + + +// rebuilds dbCount on disk from scratch by walking through actual +// curse DB and tallying counts // returns true if db left in an open state -static char cullStaleCount() { - LINEARDB3 tempDB; +static char rebuildCountDB() { - int error = LINEARDB3_open( &tempDB, - "curseCount.db.temp", + double startTime = Time::getCurrentTime(); + + + // first, close and delete our curseCount.db file + + LINEARDB3_close( &dbCount ); + + + File dbFile( NULL, "curseCount.db" ); + + dbFile.remove(); + + int error = LINEARDB3_open( &dbCount, + "curseCount.db", KISSDB_OPEN_MODE_RWCREAT, 10000, 40, 12 ); if( error ) { - AppLog::errorF( "Error %d opening curseCount temp LinearDB3", error ); - return true; + AppLog::errorF( "Error %d re-opening fresh curseCount LinearDB3", + error ); + return false; } + + + // walk through main curse db and add to counts LINEARDB3_Iterator dbi; - LINEARDB3_Iterator_init( &dbCount, &dbi ); + LINEARDB3_Iterator_init( &db, &dbi ); - unsigned char key[40]; + unsigned char key[80]; - unsigned char value[12]; + unsigned char value[8]; int total = 0; - int stale = 0; - int nonStale = 0; - - char forceStale = false; - - if( SettingsManager::getIntSetting( "clearCurseCountsOnStartup", 0 ) ) { - printf( "Culling curseCount.db clearCurseCountsOnStartup setting, " - "treating all records as stale.\n" ); - forceStale = true; - } while( LINEARDB3_Iterator_next( &dbi, key, value ) > 0 ) { - total++; + char *receiverEmail = getEmailFromKey( key, 1 ); - int count = valueToInt( value ); - - timeSec_t curseTime = valueToTime( &( value[4] ) ); - - timeSec_t elapsedTime = Time::timeSec() - curseTime; - - if( forceStale || elapsedTime > curseDuration * count ) { - // completely decremented to 0 due to elapsed time - // the newest curse record for this person is stale - // that means all of them are stale - stale ++; - } - else { - nonStale ++; - LINEARDB3_put( &tempDB, key, value ); - } + incrementCurseCount( receiverEmail ); + total ++; } - printf( "Culling curseCount.db found " - "%d total entries, %d stale, %d non-stale\n", - total, stale, nonStale ); + printf( "Rebuilding curseCount.db with %d count increments " + "took %.2f seconds\n", + total, Time::getCurrentTime() - startTime ); - - LINEARDB3_close( &tempDB ); - LINEARDB3_close( &dbCount ); - - - File dbFile( NULL, "curseCount.db" ); - File dbTempFile( NULL, "curseCount.db.temp" ); - - dbTempFile.copy( &dbFile ); - dbTempFile.remove(); - - error = LINEARDB3_open( &dbCount, - "curseCount.db", - KISSDB_OPEN_MODE_RWCREAT, - 10000, - 40, - 12 ); - - if( error ) { - AppLog::errorF( "Error %d re-opening curseCount LinearDB3", error ); - return false; - } - return true; } @@ -288,7 +286,7 @@ void initCurseDB() { return; } - dbCountOpen = cullStaleCount(); + dbCountOpen = rebuildCountDB(); } @@ -318,6 +316,10 @@ static void getKey( const char *inSenderEmail, const char *inReceiverEmail, + + + + // old key has no , between addresses // don't write new curses in this format // but check for existing curses using this format @@ -537,7 +539,8 @@ static void stepStaleCurseCulling() { -void setDBCurse( const char *inSenderEmail, const char *inReceiverEmail ) { +void setDBCurse( int inSenderID, + const char *inSenderEmail, const char *inReceiverEmail ) { checkSettings(); char alreadyCursedByThisPerson = isCursed( inSenderEmail, inReceiverEmail ); @@ -561,12 +564,18 @@ void setDBCurse( const char *inSenderEmail, const char *inReceiverEmail ) { if( ! alreadyCursedByThisPerson ) { incrementCurseCount( inReceiverEmail ); } + + + logCurse( inSenderID, (char*)inSenderEmail, (char*)inReceiverEmail ); + + logCurseScore( (char*)inReceiverEmail, getCurseCount( inReceiverEmail ) ); } -void clearDBCurse( const char *inSenderEmail, const char *inReceiverEmail ) { +void clearDBCurse( int inSenderID, + const char *inSenderEmail, const char *inReceiverEmail ) { unsigned char key[80]; unsigned char value[8]; @@ -595,6 +604,11 @@ void clearDBCurse( const char *inSenderEmail, const char *inReceiverEmail ) { LINEARDB3_put( &db, key, value ); } } + + + logUnCurse( inSenderID, (char*)inSenderEmail, (char*)inReceiverEmail ); + + logCurseScore( (char*)inReceiverEmail, getCurseCount( inReceiverEmail ) ); } diff --git a/server/curseDB.h b/server/curseDB.h index 1df8058b1..6bfd8755a 100644 --- a/server/curseDB.h +++ b/server/curseDB.h @@ -7,9 +7,11 @@ void initCurseDB(); void freeCurseDB(); -void setDBCurse( const char *inSenderEmail, const char *inReceiverEmail ); +void setDBCurse( int inSenderID, + const char *inSenderEmail, const char *inReceiverEmail ); -void clearDBCurse( const char *inSenderEmail, const char *inReceiverEmail ); +void clearDBCurse( int inSenderID, + const char *inSenderEmail, const char *inReceiverEmail ); char isCursed( const char *inSenderEmail, const char *inReceiverEmail ); diff --git a/server/curseLog.cpp b/server/curseLog.cpp index 009a896fd..a8a574c78 100644 --- a/server/curseLog.cpp +++ b/server/curseLog.cpp @@ -137,6 +137,47 @@ void logCurse( int inPlayerID, char *inPlayerEmail, +void logUnCurse( int inPlayerID, char *inPlayerEmail, + char *inTargetPlayerEmail ) { + + if( logFile != NULL ) { + stepLog(); + + if( logFile != NULL ) { + + fprintf( logFile, "F %.0f %d %s => %s\n", + Time::timeSec(), + inPlayerID, inPlayerEmail, + inTargetPlayerEmail ); + + fflush( logFile ); + } + } + } + + + + +void logTrust( int inPlayerID, char *inPlayerEmail, + char *inTargetPlayerEmail ) { + + if( logFile != NULL ) { + stepLog(); + + if( logFile != NULL ) { + + fprintf( logFile, "T %.0f %d %s => %s\n", + Time::timeSec(), + inPlayerID, inPlayerEmail, + inTargetPlayerEmail ); + + fflush( logFile ); + } + } + } + + + void logCurseScore( char *inPlayerEmail, int inCurseScore ) { diff --git a/server/curseLog.h b/server/curseLog.h index 1efaf9454..165f9b5bc 100644 --- a/server/curseLog.h +++ b/server/curseLog.h @@ -9,5 +9,12 @@ void freeCurseLog(); void logCurse( int inPlayerID, char *inPlayerEmail, char *inTargetPlayerEmail ); +void logUnCurse( int inPlayerID, char *inPlayerEmail, + char *inTargetPlayerEmail ); + + +void logTrust( int inPlayerID, char *inPlayerEmail, + char *inTargetPlayerEmail ); + void logCurseScore( char *inPlayerEmail, int inCurseScore ); diff --git a/server/curses.cpp b/server/curses.cpp index 5541c3c56..99dfbdac1 100644 --- a/server/curses.cpp +++ b/server/curses.cpp @@ -323,7 +323,6 @@ static void stepCurses() { if( r->livedTimeSinceScoreDecrement + aliveTime >= decrementTime ) { r->score --; - logCurseScore( r->email, r->score ); r->livedTimeSinceScoreDecrement = 0; r->aliveStartTimeSinceScoreDecrement = curTime; @@ -655,9 +654,6 @@ char cursePlayer( int inGiverID, int inGiverLineageEveID, char *inGiverEmail, receiverRecord->aliveStartTimeSinceScoreDecrement = curTime; } - logCurse( inGiverID, inGiverEmail, receiverRecord->email ); - - logCurseScore( receiverRecord->email, receiverRecord->score ); if( useCurseServer ) { diff --git a/server/lifeLog.cpp b/server/lifeLog.cpp index dcef6e3f0..ea3672fbd 100644 --- a/server/lifeLog.cpp +++ b/server/lifeLog.cpp @@ -7,6 +7,8 @@ #include "curses.h" +#include "../gameSource/objectBank.h" + #include "minorGems/util/stringUtils.h" @@ -143,6 +145,7 @@ static void stepLog() { void logBirth( int inPlayerID, char *inPlayerEmail, int inParentID, char *inParentEmail, char inIsMale, + int inRace, int inMapX, int inMapY, int inTotalPopulation, int inParentChainLength ) { @@ -168,12 +171,15 @@ void logBirth( int inPlayerID, char *inPlayerEmail, if( inIsMale ) { genderChar = 'M'; } + + char raceChar = (char)( inRace - 1 + 'A' ); - fprintf( logFile, "B %.f %d %s %c (%d,%d) %s pop=%d chain=%d\n", + fprintf( logFile, + "B %.f %d %s %c (%d,%d) %s pop=%d chain=%d race=%c\n", Time::timeSec(), inPlayerID, inPlayerEmail, genderChar, inMapX, inMapY, parentString, - inTotalPopulation, inParentChainLength ); + inTotalPopulation, inParentChainLength, raceChar ); fflush( logFile ); @@ -234,12 +240,40 @@ void logDeath( int inPlayerID, char *inPlayerEmail, char *causeString; if( inKillerEmail == NULL ) { - if( inDisconnect ) { - causeString = stringDuplicate( "disconnect" ); + // inDisconnect doesn't mean anything anymore + // because no one ever dies from being disconnected + // so ignore this signal + + if( inAge >= forceDeathAge ) { + causeString = stringDuplicate( "oldAge" ); } else { - if( inAge >= forceDeathAge ) { - causeString = stringDuplicate( "oldAge" ); + + if( inKillerID == inPlayerID ) { + causeString = stringDuplicate( "suicide" ); + } + else if( inKillerID < -1 ) { + + // use cleaned-up non-human object string + // as cause of death + + ObjectRecord *o = getObject( - inKillerID, true ); + if( o != NULL ) { + causeString = + stringDuplicate( o->description ); + + // terminate at comment + char *commentStart = strstr( causeString, "#" ); + if( commentStart != NULL ) { + commentStart[0] = '\0'; + } + + char *nextSpace = strstr( causeString, " " ); + while( nextSpace != NULL ) { + nextSpace[0] = '_'; + nextSpace = strstr( nextSpace, " " ); + } + } } else { causeString = stringDuplicate( "hunger" ); @@ -278,6 +312,7 @@ void logName( int inPlayerID, char *inEmail, char *inName, int inLineageEveID ) { if( nameLogFile != NULL ) { fprintf( nameLogFile, "%d %s\n", inPlayerID, inName ); + fflush( nameLogFile ); } logPlayerNameForCurses( inEmail, inName, inLineageEveID ); } diff --git a/server/lifeLog.h b/server/lifeLog.h index b8029ff71..75e58c809 100644 --- a/server/lifeLog.h +++ b/server/lifeLog.h @@ -13,6 +13,7 @@ void freeLifeLog(); void logBirth( int inPlayerID, char *inPlayerEmail, int inParentID, char *inParentEmail, char inIsMale, + int inRace, int inMapX, int inMapY, int inTotalPopulation, int inParentChainLength ); @@ -27,6 +28,8 @@ void logDeath( int inPlayerID, char *inPlayerEmail, int inMapX, int inMapY, int inTotalRemainingPopulation, char inDisconnect = false, + // can be negative objectID if death caused by non-player object + // can be own ID if died from suicide int inKillerID = -1, char *inKillerEmail = NULL ); diff --git a/server/map.cpp b/server/map.cpp index 2820ad9fa..e160fe39d 100644 --- a/server/map.cpp +++ b/server/map.cpp @@ -2513,15 +2513,56 @@ static char loadIntoMapFromFile( FILE *inFile, Time::getCurrentTime() < startTime + inTimeLimitSec ) { TestMapRecord r; - - char stringBuff[1000]; - - int numRead = fscanf( inFile, "%d %d %d %d %999s", + + + // read a string of arbitrary length (container with an unknown + // number of slots) + // Old code used a static buffer of 1000 for this, but + // that will fail for longer strings + // + // For reference, old format string for scanf was "%d %d %d %d %999s" + + + // read ints first + int numRead = fscanf( inFile, "%d %d %d %d", &(r.x), &(r.y), &(r.biome), - &(r.floor), - stringBuff ); - - if( numRead != 5 ) { + &(r.floor) ); + + if( numRead != 4 ) { + moreFileLeft = false; + break; + } + + // now skip string and measure position to get max length + int posBeforeString = ftell( inFile ); + + // skip string + fscanf( inFile, "%*s" ); + + int posAfterString = ftell( inFile ); + + // now we know how long string is + int stringLength = posAfterString - posBeforeString; + + if( stringLength <= 0 ) { + moreFileLeft = false; + break; + } + + char *stringBuff = new char[ stringLength + 1 ]; + + // rewind file to scan string + fseek( inFile, posBeforeString, SEEK_SET ); + + char *formatString = autoSprintf( "%%%ds", stringLength ); + + numRead = fscanf( inFile, formatString, stringBuff ); + + delete [] formatString; + + + if( numRead != 1 ) { + delete [] stringBuff; moreFileLeft = false; break; } @@ -2531,7 +2572,10 @@ static char loadIntoMapFromFile( FILE *inFile, int numSlots; char **slots = split( stringBuff, ",", &numSlots ); - + + delete [] stringBuff; + + for( int i=0; itapoutCountLimit != -1 ) { + // this turns the loop into a totalGridCells draws inR->tapoutCountLimit + double P = (double)(inR->tapoutCountLimit - tapoutCount) / (totalGridCells - currentGridCellIndex); + + double p = randSource.getRandomBoundedDouble( 0, 1 ); + + if( p >= P ) continue; + } if( true ) { // last use target signifies what happens in diff --git a/server/printLifeLogStatsHTML.cpp b/server/printLifeLogStatsHTML.cpp index 74413e478..e413cb452 100644 --- a/server/printLifeLogStatsHTML.cpp +++ b/server/printLifeLogStatsHTML.cpp @@ -121,7 +121,9 @@ void processLogFile( File *inFile ) { int locX, locY; char parent[1000]; char deathReason[1000]; - + + char race = '?'; + email[0] = '\0'; parent[0] = '\0'; deathReason[0] = '\0'; @@ -133,9 +135,12 @@ void processLogFile( File *inFile ) { if( event == 'B' ) { - fscanf( f, "%lf %d %999s %c (%d,%d) %999s pop=%d chain=%d\n", + // note that old-style log files might not have race= + // at end of line, which should be okay + fscanf( f, "%lf %d %999s %c (%d,%d) %999s pop=%d chain=%d " + "race=%c\n", &time, &id, email, &gender, - &locX, &locY, parent, &pop, &parentChain ); + &locX, &locY, parent, &pop, &parentChain, &race ); Living l; l.id = id; diff --git a/server/server.cpp b/server/server.cpp index 9a70348ec..0d1f17108 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -764,6 +764,25 @@ typedef struct LiveObject { char newMove; + // Absolute position used when generating last PU sent out about this + // player. + // If they are making a very long chained move, and their status + // isn't changing, they might not generate a PU message for a very + // long time. This becomes a problem when them move out/in range + // of another player. If their status (held item, etc) has changed + // while they are out of range, the other player won't see that + // status change when they come back in range (because the PU + // happened when they were out of range) and the long chained move + // isn't generating any PU messages now that they are back in range. + // Since modded clients might make very long MOVEs for each part + // of a MOVE chain (since they are zoomed out), we can't just count + // MOVE messages sent since the las PU message went out. + // We use this position to determine how far they've moved away + // from their last PU position, and send an intermediary PU if + // they get too far away + GridPos lastPlayerUpdateAbsolutePos; + + // heat map that player carries around with them // every time they stop moving, it is updated to compute // their local temp @@ -5041,7 +5060,11 @@ static void makePlayerSay( LiveObject *inPlayer, char *inToSay, bool inPrivate = if( strcmp( inToSay, curseYouPhrase ) == 0 ) { isYouShortcut = true; } - if( strcmp( inToSay, curseBabyPhrase ) == 0 ) { + + if( strcmp( inToSay, curseBabyPhrase ) == 0 + && + SettingsManager::getIntSetting( "allowBabyCursing", 0 ) ) { + isBabyShortcut = true; } @@ -5244,7 +5267,7 @@ static void makePlayerSay( LiveObject *inPlayer, char *inToSay, bool inPrivate = if( isCurse ) { char *targetEmail = getCurseReceiverEmail( cursedName ); if( targetEmail != NULL ) { - setDBCurse( inPlayer->email, targetEmail ); + setDBCurse( inPlayer->id, inPlayer->email, targetEmail ); dbCurseTargetEmail = targetEmail; } } @@ -5264,7 +5287,7 @@ static void makePlayerSay( LiveObject *inPlayer, char *inToSay, bool inPrivate = spendCurseToken( inPlayer->email ) ) { isCurse = true; - setDBCurse( inPlayer->email, youCursePlayer->email ); + setDBCurse( inPlayer->id, inPlayer->email, youCursePlayer->email ); dbCurseTargetEmail = youCursePlayer->email; } else if( isBabyShortcut && babyCursePlayer != NULL && @@ -5278,7 +5301,7 @@ static void makePlayerSay( LiveObject *inPlayer, char *inToSay, bool inPrivate = targetEmail = babyCursePlayer->origEmail; } if( targetEmail != NULL ) { - setDBCurse( inPlayer->email, targetEmail ); + setDBCurse( inPlayer->id, inPlayer->email, targetEmail ); dbCurseTargetEmail = targetEmail; } } @@ -5288,7 +5311,8 @@ static void makePlayerSay( LiveObject *inPlayer, char *inToSay, bool inPrivate = isCurse = true; - setDBCurse( inPlayer->email, inPlayer->lastBabyEmail ); + setDBCurse( inPlayer->id, + inPlayer->email, inPlayer->lastBabyEmail ); dbCurseTargetEmail = inPlayer->lastBabyEmail; } } @@ -6302,6 +6326,9 @@ static UpdateRecord getUpdateRecord( inPlayer->posForced ); r.absolutePosX = x; r.absolutePosY = y; + + inPlayer->lastPlayerUpdateAbsolutePos.x = x; + inPlayer->lastPlayerUpdateAbsolutePos.y = y; } SimpleVector clothingListBuffer; @@ -6975,6 +7002,8 @@ int processLoggedInPlayer( char inAllowReconnect, o->connected = true; o->cravingKnown = false; + o->curseTokenUpdate = true; + if( o->heldByOther ) { // they're held, so they may have moved far away from their // original location @@ -8138,6 +8167,10 @@ int processLoggedInPlayer( char inAllowReconnect, newObject.deathLogged = false; newObject.newMove = false; + newObject.lastPlayerUpdateAbsolutePos.x = 0; + newObject.lastPlayerUpdateAbsolutePos.y = 0; + + newObject.posForced = false; newObject.waitingForForceResponse = false; @@ -8416,6 +8449,7 @@ int processLoggedInPlayer( char inAllowReconnect, newObject.parentID, parentEmail, ! getFemale( &newObject ), + getObject( newObject.displayID )->race, newObject.xd, newObject.yd, players.size(), @@ -8885,54 +8919,77 @@ static char isContainmentWithMatchedTags( int inContainerID, int inContainedID ) // not a limited containable object return false; } + + char anyWithLimitNameFound = false; + + while( contLoc != NULL ) { + - char *limitNameLoc = &( contLoc[5] ); + char *limitNameLoc = &( contLoc[5] ); - if( limitNameLoc[0] != ' ' && - limitNameLoc[0] != '\0' ) { + if( limitNameLoc[0] != ' ' && + limitNameLoc[0] != '\0' ) { - // there's something after +cont - // scan the whole thing, including +cont + // there's something after +cont + // scan the whole thing, including +cont + + anyWithLimitNameFound = true; - char tag[100]; - - int numRead = sscanf( contLoc, "%99s", tag ); - - if( numRead == 1 ) { + char tag[100]; - // clean up # character that might delimit end of string - int tagLen = strlen( tag ); + int numRead = sscanf( contLoc, "%99s", tag ); - for( int i=0; idescription, tag ); - - if( locInContainerName != NULL ) { - // skip to end of tag - // and make sure tag isn't a sub-tag of container tag - // don't want contained to be +contHot - // and contaienr to be +contHotPlates - char end = locInContainerName[ tagLen ]; + char *locInContainerName = + strstr( getObject( inContainerID )->description, tag ); - if( end == ' ' || - end == '\0'|| - end == '#' ) { - return true; + if( locInContainerName != NULL ) { + // skip to end of tag + // and make sure tag isn't a sub-tag of container tag + // don't want contained to be +contHot + // and contaienr to be +contHotPlates + + char end = locInContainerName[ tagLen ]; + + if( end == ' ' || + end == '\0'|| + end == '#' ) { + return true; + } } + // no match with this container so far, + // but we can keep trying other +cont tags + // in our contained object } - return false; } + else { + // +cont with nothing after it, no limit based on this tag + } + + // keep looking beyond last limit loc + contLoc = strstr( limitNameLoc, "+cont" ); + } + + if( anyWithLimitNameFound ) { + // item is limited to some types of container, and this + // container didn't match any of the limit names + return false; } - // +cont with nothing after it, no limit + + // we get here if we found +cont in the item, but no limit name after it return false; } @@ -15908,6 +15965,19 @@ int main() { nextPlayer->xd = m.extraPos[ m.numExtraPos - 1].x; nextPlayer->yd = m.extraPos[ m.numExtraPos - 1].y; + + if( distance( nextPlayer->lastPlayerUpdateAbsolutePos, + m.extraPos[ m.numExtraPos - 1] ) + > + getMaxChunkDimension() / 2 ) { + // they have moved a long way since their + // last PU was sent + // Send one now, mid-move + + playerIndicesToSendUpdatesAbout.push_back( i ); + } + + if( nextPlayer->xd == nextPlayer->xs && nextPlayer->yd == nextPlayer->ys ) { @@ -16420,7 +16490,8 @@ int main() { } if( otherToForgive != NULL ) { - clearDBCurse( nextPlayer->email, + clearDBCurse( nextPlayer->id, + nextPlayer->email, otherToForgive->email ); char *message = @@ -20153,6 +20224,19 @@ int main() { if( ! nextPlayer->isTutorial ) { + GridPos deathPos = + getPlayerPos( nextPlayer ); + + int killerID = -1; + if( nextPlayer->murderPerpID > 0 ) { + killerID = nextPlayer->murderPerpID; + } + else if( nextPlayer->deathSourceID > 0 ) { + // include as negative of ID + killerID = - nextPlayer->deathSourceID; + } + // never have suicide in this case + logDeath( nextPlayer->id, nextPlayer->email, nextPlayer->isEve, @@ -20163,7 +20247,7 @@ int main() { nextPlayer->xd, nextPlayer->yd, players.size() - 1, false, - nextPlayer->murderPerpID, + killerID, nextPlayer->murderPerpEmail ); if( shutdownMode ) { @@ -20457,7 +20541,7 @@ int main() { dropPos.x, dropPos.y, players.size() - 1, disconnect, - nextPlayer->murderPerpID, + killerID, nextPlayer->murderPerpEmail ); if( shutdownMode ) { @@ -21547,6 +21631,23 @@ int main() { if( ! decrementedPlayer->deathLogged && ! decrementedPlayer->isTutorial ) { + + + // yes, they starved to death here + // but also log case where they were wounded + // before starving. Thus, the true cause + // of death should be the wounding that made + // them helpless and caused them to starve + int killerID = -1; + if( nextPlayer->murderPerpID > 0 ) { + killerID = nextPlayer->murderPerpID; + } + else if( nextPlayer->deathSourceID > 0 ) { + // include as negative of ID + killerID = - nextPlayer->deathSourceID; + } + // never have suicide in this case + logDeath( decrementedPlayer->id, decrementedPlayer->email, decrementedPlayer->isEve, @@ -21556,7 +21657,7 @@ int main() { deathPos.x, deathPos.y, players.size() - 1, false, - nextPlayer->murderPerpID, + killerID, nextPlayer->murderPerpEmail ); } diff --git a/server/settings/allowBabyCursing.ini b/server/settings/allowBabyCursing.ini new file mode 100644 index 000000000..c22708346 --- /dev/null +++ b/server/settings/allowBabyCursing.ini @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/server/settings/clearCurseCountsOnStartup.ini b/server/settings/clearCurseCountsOnStartup.ini index 56a6051ca..c22708346 100644 --- a/server/settings/clearCurseCountsOnStartup.ini +++ b/server/settings/clearCurseCountsOnStartup.ini @@ -1 +1 @@ -1 \ No newline at end of file +0 \ No newline at end of file