diff --git a/core/data/src/main/java/ru/tech/imageresizershrinker/core/data/di/DispatchersModule.kt b/core/data/src/main/java/ru/tech/imageresizershrinker/core/data/di/DispatchersModule.kt index a936e0b95..7d772f711 100644 --- a/core/data/src/main/java/ru/tech/imageresizershrinker/core/data/di/DispatchersModule.kt +++ b/core/data/src/main/java/ru/tech/imageresizershrinker/core/data/di/DispatchersModule.kt @@ -21,7 +21,7 @@ import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent -import ru.tech.imageresizershrinker.core.data.dispatchers.DispatchersHolderImpl +import ru.tech.imageresizershrinker.core.data.dispatchers.AndroidDispatchersHolder import ru.tech.imageresizershrinker.core.domain.dispatchers.DispatchersHolder import javax.inject.Singleton @@ -32,7 +32,7 @@ internal interface DispatchersModule { @Binds @Singleton fun dispatchersHolder( - dispatchers: DispatchersHolderImpl + dispatchers: AndroidDispatchersHolder ): DispatchersHolder } \ No newline at end of file diff --git a/core/data/src/main/java/ru/tech/imageresizershrinker/core/data/dispatchers/DispatchersHolderImpl.kt b/core/data/src/main/java/ru/tech/imageresizershrinker/core/data/dispatchers/AndroidDispatchersHolder.kt similarity index 94% rename from core/data/src/main/java/ru/tech/imageresizershrinker/core/data/dispatchers/DispatchersHolderImpl.kt rename to core/data/src/main/java/ru/tech/imageresizershrinker/core/data/dispatchers/AndroidDispatchersHolder.kt index d4be87a81..9b93fbb37 100644 --- a/core/data/src/main/java/ru/tech/imageresizershrinker/core/data/dispatchers/DispatchersHolderImpl.kt +++ b/core/data/src/main/java/ru/tech/imageresizershrinker/core/data/dispatchers/AndroidDispatchersHolder.kt @@ -26,7 +26,7 @@ import ru.tech.imageresizershrinker.core.di.UiDispatcher import ru.tech.imageresizershrinker.core.domain.dispatchers.DispatchersHolder import javax.inject.Inject -internal data class DispatchersHolderImpl @Inject constructor( +internal data class AndroidDispatchersHolder @Inject constructor( @UiDispatcher override val uiDispatcher: CoroutineDispatcher, @IoDispatcher override val ioDispatcher: CoroutineDispatcher, @EncodingDispatcher override val encodingDispatcher: CoroutineDispatcher, diff --git a/core/domain/src/main/kotlin/ru/tech/imageresizershrinker/core/domain/model/IntegerSize.kt b/core/domain/src/main/kotlin/ru/tech/imageresizershrinker/core/domain/model/IntegerSize.kt index 031baf21f..cff931df8 100644 --- a/core/domain/src/main/kotlin/ru/tech/imageresizershrinker/core/domain/model/IntegerSize.kt +++ b/core/domain/src/main/kotlin/ru/tech/imageresizershrinker/core/domain/model/IntegerSize.kt @@ -31,6 +31,11 @@ data class IntegerSize( value }.getOrNull() ?: 1f + val safeAspectRatio: Float + get() = aspectRatio + .coerceAtLeast(0.005f) + .coerceAtMost(1000f) + operator fun times(i: Float): IntegerSize = IntegerSize( width = (width * i).toInt(), height = (height * i).toInt() diff --git a/core/filters/src/main/java/ru/tech/imageresizershrinker/core/filters/domain/model/Rounding.kt b/core/domain/src/main/kotlin/ru/tech/imageresizershrinker/core/domain/utils/Rounding.kt similarity index 89% rename from core/filters/src/main/java/ru/tech/imageresizershrinker/core/filters/domain/model/Rounding.kt rename to core/domain/src/main/kotlin/ru/tech/imageresizershrinker/core/domain/utils/Rounding.kt index 921c9decf..61adf4b72 100644 --- a/core/filters/src/main/java/ru/tech/imageresizershrinker/core/filters/domain/model/Rounding.kt +++ b/core/domain/src/main/kotlin/ru/tech/imageresizershrinker/core/domain/utils/Rounding.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package ru.tech.imageresizershrinker.core.filters.domain.model +package ru.tech.imageresizershrinker.core.domain.utils import kotlin.math.pow import kotlin.math.roundToInt diff --git a/core/filters/src/main/java/ru/tech/imageresizershrinker/core/filters/presentation/model/UiLinearGaussianBlurFilter.kt b/core/filters/src/main/java/ru/tech/imageresizershrinker/core/filters/presentation/model/UiLinearGaussianBlurFilter.kt index 142c85340..792e47bff 100644 --- a/core/filters/src/main/java/ru/tech/imageresizershrinker/core/filters/presentation/model/UiLinearGaussianBlurFilter.kt +++ b/core/filters/src/main/java/ru/tech/imageresizershrinker/core/filters/presentation/model/UiLinearGaussianBlurFilter.kt @@ -17,10 +17,10 @@ package ru.tech.imageresizershrinker.core.filters.presentation.model +import ru.tech.imageresizershrinker.core.domain.utils.NEAREST_ODD_ROUNDING import ru.tech.imageresizershrinker.core.filters.domain.model.Filter import ru.tech.imageresizershrinker.core.filters.domain.model.FilterParam import ru.tech.imageresizershrinker.core.filters.domain.model.LinearGaussianParams -import ru.tech.imageresizershrinker.core.filters.domain.model.NEAREST_ODD_ROUNDING import ru.tech.imageresizershrinker.core.resources.R class UiLinearGaussianBlurFilter( diff --git a/core/filters/src/main/java/ru/tech/imageresizershrinker/core/filters/presentation/model/UiLinearTentBlurFilter.kt b/core/filters/src/main/java/ru/tech/imageresizershrinker/core/filters/presentation/model/UiLinearTentBlurFilter.kt index 0edf2c79d..e9a8e1eeb 100644 --- a/core/filters/src/main/java/ru/tech/imageresizershrinker/core/filters/presentation/model/UiLinearTentBlurFilter.kt +++ b/core/filters/src/main/java/ru/tech/imageresizershrinker/core/filters/presentation/model/UiLinearTentBlurFilter.kt @@ -17,9 +17,9 @@ package ru.tech.imageresizershrinker.core.filters.presentation.model +import ru.tech.imageresizershrinker.core.domain.utils.NEAREST_ODD_ROUNDING import ru.tech.imageresizershrinker.core.filters.domain.model.Filter import ru.tech.imageresizershrinker.core.filters.domain.model.FilterParam -import ru.tech.imageresizershrinker.core.filters.domain.model.NEAREST_ODD_ROUNDING import ru.tech.imageresizershrinker.core.filters.domain.model.TransferFunc import ru.tech.imageresizershrinker.core.resources.R diff --git a/core/filters/src/main/java/ru/tech/imageresizershrinker/core/filters/presentation/model/UiTentBlurFilter.kt b/core/filters/src/main/java/ru/tech/imageresizershrinker/core/filters/presentation/model/UiTentBlurFilter.kt index d8a759b8d..c4f1506f7 100644 --- a/core/filters/src/main/java/ru/tech/imageresizershrinker/core/filters/presentation/model/UiTentBlurFilter.kt +++ b/core/filters/src/main/java/ru/tech/imageresizershrinker/core/filters/presentation/model/UiTentBlurFilter.kt @@ -17,9 +17,9 @@ package ru.tech.imageresizershrinker.core.filters.presentation.model +import ru.tech.imageresizershrinker.core.domain.utils.NEAREST_ODD_ROUNDING import ru.tech.imageresizershrinker.core.filters.domain.model.Filter import ru.tech.imageresizershrinker.core.filters.domain.model.FilterParam -import ru.tech.imageresizershrinker.core.filters.domain.model.NEAREST_ODD_ROUNDING import ru.tech.imageresizershrinker.core.resources.R class UiTentBlurFilter( diff --git a/core/filters/src/main/java/ru/tech/imageresizershrinker/core/filters/presentation/widget/FilterItemContent.kt b/core/filters/src/main/java/ru/tech/imageresizershrinker/core/filters/presentation/widget/FilterItemContent.kt index 40e2d1821..0153d30f1 100644 --- a/core/filters/src/main/java/ru/tech/imageresizershrinker/core/filters/presentation/widget/FilterItemContent.kt +++ b/core/filters/src/main/java/ru/tech/imageresizershrinker/core/filters/presentation/widget/FilterItemContent.kt @@ -49,6 +49,7 @@ import androidx.compose.ui.unit.dp import ru.tech.imageresizershrinker.core.domain.model.ColorModel import ru.tech.imageresizershrinker.core.domain.model.FileModel import ru.tech.imageresizershrinker.core.domain.model.ImageModel +import ru.tech.imageresizershrinker.core.domain.utils.roundTo import ru.tech.imageresizershrinker.core.filters.domain.model.BlurEdgeMode import ru.tech.imageresizershrinker.core.filters.domain.model.BokehParams import ru.tech.imageresizershrinker.core.filters.domain.model.ClaheParams @@ -65,7 +66,6 @@ import ru.tech.imageresizershrinker.core.filters.domain.model.RadialTiltShiftPar import ru.tech.imageresizershrinker.core.filters.domain.model.SideFadeParams import ru.tech.imageresizershrinker.core.filters.domain.model.TransferFunc import ru.tech.imageresizershrinker.core.filters.domain.model.WaterParams -import ru.tech.imageresizershrinker.core.filters.domain.model.roundTo import ru.tech.imageresizershrinker.core.filters.domain.model.wrap import ru.tech.imageresizershrinker.core.filters.presentation.model.UiColorFilter import ru.tech.imageresizershrinker.core.filters.presentation.model.UiFilter diff --git a/core/resources/src/main/res/values/strings.xml b/core/resources/src/main/res/values/strings.xml index c414ec11f..1e370a345 100644 --- a/core/resources/src/main/res/values/strings.xml +++ b/core/resources/src/main/res/values/strings.xml @@ -1387,4 +1387,19 @@ Show All Hide Nav Bar Hide Status Bar + Noise Generation + Generate different noises like Perlin or other types + Frequency + Noise Type + Rotation Type + Fractal Type + Octaves + Lacunarity + Gain + Weighted Strength + Ping Pong Strength + Distance Function + Return Type + Jitter + Domain Warp \ No newline at end of file diff --git a/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/utils/navigation/Screen.kt b/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/utils/navigation/Screen.kt index 02ef52e19..570e8d66d 100644 --- a/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/utils/navigation/Screen.kt +++ b/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/utils/navigation/Screen.kt @@ -36,6 +36,7 @@ import androidx.compose.material.icons.outlined.FilterHdr import androidx.compose.material.icons.outlined.FolderZip import androidx.compose.material.icons.outlined.GifBox import androidx.compose.material.icons.outlined.Gradient +import androidx.compose.material.icons.outlined.Grain import androidx.compose.material.icons.outlined.Photo import androidx.compose.material.icons.outlined.PictureAsPdf import androidx.compose.material.icons.outlined.QrCode @@ -116,6 +117,7 @@ sealed class Screen( is ImageSplitting -> "Image_Splitting" is ColorTools -> "Color_Tools" is WebpTools -> "WEBP_Tools" + is NoiseGeneration -> "Noise_Generation" } val icon: ImageVector? @@ -156,6 +158,7 @@ sealed class Screen( is ImageSplitting -> Icons.Outlined.ContentCut ColorTools -> Icons.Outlined.ColorLens is WebpTools -> Icons.Rounded.WebpBox + NoiseGeneration -> Icons.Outlined.Grain } data object Settings : Screen( @@ -698,6 +701,12 @@ sealed class Screen( } } + data object NoiseGeneration : Screen( + id = 32, + title = R.string.noise_generation, + subtitle = R.string.noise_generation_sub + ) + companion object { val typedEntries by lazy { listOf( @@ -750,6 +759,7 @@ sealed class Screen( JxlTools(), ApngTools(), Cipher(), + NoiseGeneration, Zip(), WebpTools() ) to Triple( @@ -763,6 +773,6 @@ sealed class Screen( typedEntries.flatMap { it.first }.sortedBy { it.id } } - const val FEATURES_COUNT = 51 + const val FEATURES_COUNT = 52 } } \ No newline at end of file diff --git a/feature/filters/src/main/java/ru/tech/imageresizershrinker/feature/filters/data/FavoriteFiltersInteractorImpl.kt b/feature/filters/src/main/java/ru/tech/imageresizershrinker/feature/filters/data/AndroidFavoriteFiltersInteractor.kt similarity index 99% rename from feature/filters/src/main/java/ru/tech/imageresizershrinker/feature/filters/data/FavoriteFiltersInteractorImpl.kt rename to feature/filters/src/main/java/ru/tech/imageresizershrinker/feature/filters/data/AndroidFavoriteFiltersInteractor.kt index dcf20131a..32b796794 100644 --- a/feature/filters/src/main/java/ru/tech/imageresizershrinker/feature/filters/data/FavoriteFiltersInteractorImpl.kt +++ b/feature/filters/src/main/java/ru/tech/imageresizershrinker/feature/filters/data/AndroidFavoriteFiltersInteractor.kt @@ -51,7 +51,7 @@ import ru.tech.imageresizershrinker.feature.filters.di.FilterInteractorDataStore import javax.inject.Inject import kotlin.reflect.full.primaryConstructor -internal class FavoriteFiltersInteractorImpl @Inject constructor( +internal class AndroidFavoriteFiltersInteractor @Inject constructor( @ApplicationContext private val context: Context, @FilterInteractorDataStore private val dataStore: DataStore, private val fileController: FileController diff --git a/feature/filters/src/main/java/ru/tech/imageresizershrinker/feature/filters/data/model/LinearGaussianBlurFilter.kt b/feature/filters/src/main/java/ru/tech/imageresizershrinker/feature/filters/data/model/LinearGaussianBlurFilter.kt index 48dda0c96..992f6fc08 100644 --- a/feature/filters/src/main/java/ru/tech/imageresizershrinker/feature/filters/data/model/LinearGaussianBlurFilter.kt +++ b/feature/filters/src/main/java/ru/tech/imageresizershrinker/feature/filters/data/model/LinearGaussianBlurFilter.kt @@ -21,10 +21,10 @@ import android.graphics.Bitmap import com.awxkee.aire.Aire import ru.tech.imageresizershrinker.core.domain.model.IntegerSize import ru.tech.imageresizershrinker.core.domain.transformation.Transformation +import ru.tech.imageresizershrinker.core.domain.utils.NEAREST_ODD_ROUNDING +import ru.tech.imageresizershrinker.core.domain.utils.roundTo import ru.tech.imageresizershrinker.core.filters.domain.model.Filter import ru.tech.imageresizershrinker.core.filters.domain.model.LinearGaussianParams -import ru.tech.imageresizershrinker.core.filters.domain.model.NEAREST_ODD_ROUNDING -import ru.tech.imageresizershrinker.core.filters.domain.model.roundTo import ru.tech.imageresizershrinker.feature.filters.data.utils.toEdgeMode import ru.tech.imageresizershrinker.feature.filters.data.utils.toFunc diff --git a/feature/filters/src/main/java/ru/tech/imageresizershrinker/feature/filters/di/FilterModule.kt b/feature/filters/src/main/java/ru/tech/imageresizershrinker/feature/filters/di/FilterModule.kt index 8fd09edb7..573699c76 100644 --- a/feature/filters/src/main/java/ru/tech/imageresizershrinker/feature/filters/di/FilterModule.kt +++ b/feature/filters/src/main/java/ru/tech/imageresizershrinker/feature/filters/di/FilterModule.kt @@ -33,9 +33,9 @@ import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import ru.tech.imageresizershrinker.core.filters.domain.FavoriteFiltersInteractor import ru.tech.imageresizershrinker.core.filters.domain.FilterProvider +import ru.tech.imageresizershrinker.feature.filters.data.AndroidFavoriteFiltersInteractor import ru.tech.imageresizershrinker.feature.filters.data.AndroidFilterMaskApplier import ru.tech.imageresizershrinker.feature.filters.data.AndroidFilterProvider -import ru.tech.imageresizershrinker.feature.filters.data.FavoriteFiltersInteractorImpl import ru.tech.imageresizershrinker.feature.filters.domain.FilterMaskApplier import javax.inject.Singleton @@ -72,7 +72,7 @@ internal interface FilterModule { @Singleton @Binds fun favoriteFiltersInteractor( - interactor: FavoriteFiltersInteractorImpl + interactor: AndroidFavoriteFiltersInteractor ): FavoriteFiltersInteractor } \ No newline at end of file diff --git a/feature/noise-generation/.gitignore b/feature/noise-generation/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/feature/noise-generation/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/noise-generation/build.gradle.kts b/feature/noise-generation/build.gradle.kts new file mode 100644 index 000000000..9ab581d09 --- /dev/null +++ b/feature/noise-generation/build.gradle.kts @@ -0,0 +1,8 @@ +plugins { + alias(libs.plugins.image.toolbox.library) + alias(libs.plugins.image.toolbox.feature) + alias(libs.plugins.image.toolbox.hilt) + alias(libs.plugins.image.toolbox.compose) +} + +android.namespace = "ru.tech.imageresizershrinker.feature.noise_generation" \ No newline at end of file diff --git a/feature/noise-generation/src/main/AndroidManifest.xml b/feature/noise-generation/src/main/AndroidManifest.xml new file mode 100644 index 000000000..568741e54 --- /dev/null +++ b/feature/noise-generation/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/data/AndroidNoiseGenerator.kt b/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/data/AndroidNoiseGenerator.kt new file mode 100644 index 000000000..4c6275384 --- /dev/null +++ b/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/data/AndroidNoiseGenerator.kt @@ -0,0 +1,75 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package ru.tech.imageresizershrinker.noise_generation.data + +import android.graphics.Bitmap +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.lerp +import androidx.compose.ui.graphics.toArgb +import kotlinx.coroutines.withContext +import ru.tech.imageresizershrinker.core.domain.dispatchers.DispatchersHolder +import ru.tech.imageresizershrinker.noise_generation.domain.NoiseGenerator +import ru.tech.imageresizershrinker.noise_generation.domain.model.NoiseParams +import javax.inject.Inject + +internal class AndroidNoiseGenerator @Inject constructor( + dispatchersHolder: DispatchersHolder +) : NoiseGenerator, DispatchersHolder by dispatchersHolder { + + override suspend fun generateNoise( + width: Int, + height: Int, + noiseParams: NoiseParams, + onFailure: (Throwable) -> Unit + ): Bitmap? = withContext(defaultDispatcher) { + val generator = with(noiseParams) { + FastNoiseLite().apply { + setSeed(seed) + setFrequency(frequency) + setNoiseType(noiseType) + setRotationType3D(rotationType3D) + setFractalType(fractalType) + setFractalOctaves(fractalOctaves) + setFractalLacunarity(fractalLacunarity) + setFractalGain(fractalGain) + setFractalWeightedStrength(fractalWeightedStrength) + setFractalPingPongStrength(fractalPingPongStrength) + setCellularDistanceFunction(cellularDistanceFunction) + setCellularReturnType(cellularReturnType) + setCellularJitter(cellularJitter) + setDomainWarpType(domainWarpType) + setDomainWarpAmp(domainWarpAmp) + } + } + + runCatching { + val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + + for (x in 0.. + /// Create new FastNoise object with default seed + /// + public FastNoiseLite() { + } + + /// + /// Create new FastNoise object with specified seed + /// + public FastNoiseLite(int seed) { + setSeed(seed); + } + + private static float FastMin(float a, float b) { + return Math.min(a, b); + } + + private static float FastMax(float a, float b) { + return Math.max(a, b); + } + + private static float FastAbs(float f) { + return f < 0 ? -f : f; + } + + private static float FastSqrt(float f) { + return (float) Math.sqrt(f); + } + + private static int FastFloor(/*FNLfloat*/ float f) { + return f >= 0 ? (int) f : (int) f - 1; + } + + private static int FastRound(/*FNLfloat*/ float f) { + return f >= 0 ? (int) (f + 0.5f) : (int) (f - 0.5f); + } + + private static float Lerp(float a, float b, float t) { + return a + t * (b - a); + } + + private static float InterpHermite(float t) { + return t * t * (3 - 2 * t); + } + + private static float InterpQuintic(float t) { + return t * t * t * (t * (t * 6 - 15) + 10); + } + + private static float CubicLerp(float a, float b, float c, float d, float t) { + float p = (d - c) - (a - b); + return t * t * t * p + t * t * ((a - b) - p) + t * (c - a) + b; + } + + private static float PingPong(float t) { + t -= (int) (t * 0.5f) * 2; + return t < 1 ? t : 2 - t; + } + + private static int Hash(int seed, int xPrimed, int yPrimed) { + int hash = seed ^ xPrimed ^ yPrimed; + + hash *= 0x27d4eb2d; + return hash; + } + + private static int Hash(int seed, int xPrimed, int yPrimed, int zPrimed) { + int hash = seed ^ xPrimed ^ yPrimed ^ zPrimed; + + hash *= 0x27d4eb2d; + return hash; + } + + private static float ValCoord(int seed, int xPrimed, int yPrimed) { + int hash = Hash(seed, xPrimed, yPrimed); + + hash *= hash; + hash ^= hash << 19; + return hash * (1 / 2147483648.0f); + } + + private static float ValCoord(int seed, int xPrimed, int yPrimed, int zPrimed) { + int hash = Hash(seed, xPrimed, yPrimed, zPrimed); + + hash *= hash; + hash ^= hash << 19; + return hash * (1 / 2147483648.0f); + } + + private static float gradCoord(int seed, int xPrimed, int yPrimed, float xd, float yd) { + int hash = Hash(seed, xPrimed, yPrimed); + hash ^= hash >> 15; + hash &= 127 << 1; + + float xg = Gradients2D[hash]; + float yg = Gradients2D[hash | 1]; + + return xd * xg + yd * yg; + } + + private static float gradCoord(int seed, int xPrimed, int yPrimed, int zPrimed, float xd, float yd, float zd) { + int hash = Hash(seed, xPrimed, yPrimed, zPrimed); + hash ^= hash >> 15; + hash &= 63 << 2; + + float xg = Gradients3D[hash]; + float yg = Gradients3D[hash | 1]; + float zg = Gradients3D[hash | 2]; + + return xd * xg + yd * yg + zd * zg; + } + + /// + /// Sets seed used for all noise types + /// + /// + /// Default: 1337 + /// + public void setSeed(int seed) { + mSeed = seed; + } + + /// + /// Sets frequency for all noise types + /// + /// + /// Default: 0.01 + /// + public void setFrequency(float frequency) { + mFrequency = frequency; + } + + /// + /// Sets noise algorithm used for GetNoise(...) + /// + /// + /// Default: OpenSimplex2 + /// + public void setNoiseType(NoiseType noiseType) { + mNoiseType = noiseType; + UpdateTransformType3D(); + } + + /// + /// Sets domain rotation type for 3D Noise and 3D DomainWarp. + /// Can aid in reducing directional artifacts when sampling a 2D plane in 3D + /// + /// + /// Default: None + /// + public void setRotationType3D(RotationType3D rotationType3D) { + mRotationType3D = rotationType3D; + UpdateTransformType3D(); + UpdateWarpTransformType3D(); + } + + /// + /// Sets method for combining octaves in all fractal noise types + /// + /// + /// Default: None + /// Note: FractalType.DomainWarp... only affects DomainWarp(...) + /// + public void setFractalType(FractalType fractalType) { + mFractalType = fractalType; + } + + /// + /// Sets octave count for all fractal noise types + /// + /// + /// Default: 3 + /// + public void setFractalOctaves(int octaves) { + mOctaves = octaves; + CalculateFractalBounding(); + } + + /// + /// Sets octave lacunarity for all fractal noise types + /// + /// + /// Default: 2.0 + /// + public void setFractalLacunarity(float lacunarity) { + mLacunarity = lacunarity; + } + + /// + /// Sets octave gain for all fractal noise types + /// + /// + /// Default: 0.5 + /// + public void setFractalGain(float gain) { + mGain = gain; + CalculateFractalBounding(); + } + + /// + /// Sets octave weighting for all none DomainWarp fratal types + /// + /// + /// Default: 0.0 + /// Note: Keep between 0...1 to maintain -1...1 output bounding + /// + public void setFractalWeightedStrength(float weightedStrength) { + mWeightedStrength = weightedStrength; + } + + /// + /// Sets strength of the fractal ping pong effect + /// + /// + /// Default: 2.0 + /// + public void setFractalPingPongStrength(float pingPongStrength) { + mPingPongStrength = pingPongStrength; + } + + /// + /// Sets distance function used in cellular noise calculations + /// + /// + /// Default: Distance + /// + public void setCellularDistanceFunction(CellularDistanceFunction cellularDistanceFunction) { + mCellularDistanceFunction = cellularDistanceFunction; + } + + /// + /// Sets return type from cellular noise calculations + /// + /// + /// Default: EuclideanSq + /// + public void setCellularReturnType(CellularReturnType cellularReturnType) { + mCellularReturnType = cellularReturnType; + } + + /// + /// Sets the maximum distance a cellular point can move from it's grid position + /// + /// + /// Default: 1.0 + /// Note: Setting this higher than 1 will cause artifacts + /// + public void setCellularJitter(float cellularJitter) { + mCellularJitterModifier = cellularJitter; + } + + /// + /// Sets the warp algorithm when using DomainWarp(...) + /// + /// + /// Default: OpenSimplex2 + /// + public void setDomainWarpType(DomainWarpType domainWarpType) { + mDomainWarpType = domainWarpType; + UpdateWarpTransformType3D(); + } + + /// + /// Sets the maximum warp distance from original position when using DomainWarp(...) + /// + /// + /// Default: 1.0 + /// + public void setDomainWarpAmp(float domainWarpAmp) { + mDomainWarpAmp = domainWarpAmp; + } + + /// + /// 2D noise at given position using current settings + /// + /// + /// Noise output bounded between -1...1 + /// + public float getNoise(/*FNLfloat*/ float x, /*FNLfloat*/ float y) { + x *= mFrequency; + y *= mFrequency; + + switch (mNoiseType) { + case OpenSimplex2: + case OpenSimplex2S: { + final /*FNLfloat*/ float SQRT3 = (/*FNLfloat*/ float) 1.7320508075688772935274463415059; + final /*FNLfloat*/ float F2 = 0.5f * (SQRT3 - 1); + /*FNLfloat*/ + float t = (x + y) * F2; + x += t; + y += t; + } + break; + default: + break; + } + + return switch (mFractalType) { + default -> genNoiseSingle(mSeed, x, y); + case FBm -> genFractalFBm(x, y); + case Ridged -> genFractalRidged(x, y); + case PingPong -> genFractalPingPong(x, y); + }; + } + + /// + /// 3D noise at given position using current settings + /// + /// + /// Noise output bounded between -1...1 + /// + public float getNoise(/*FNLfloat*/ float x, /*FNLfloat*/ float y, /*FNLfloat*/ float z) { + x *= mFrequency; + y *= mFrequency; + z *= mFrequency; + + switch (mTransformType3D) { + case ImproveXYPlanes: { + /*FNLfloat*/ + float xy = x + y; + /*FNLfloat*/ + float s2 = xy * -(/*FNLfloat*/ float) 0.211324865405187; + z *= (/*FNLfloat*/ float) 0.577350269189626; + x += s2 - z; + y = y + s2 - z; + z += xy * (/*FNLfloat*/ float) 0.577350269189626; + } + break; + case ImproveXZPlanes: { + /*FNLfloat*/ + float xz = x + z; + /*FNLfloat*/ + float s2 = xz * -(/*FNLfloat*/ float) 0.211324865405187; + y *= (/*FNLfloat*/ float) 0.577350269189626; + x += s2 - y; + z += s2 - y; + y += xz * (/*FNLfloat*/ float) 0.577350269189626; + } + break; + case DefaultOpenSimplex2: { + final /*FNLfloat*/ float R3 = (/*FNLfloat*/ float) (2.0 / 3.0); + /*FNLfloat*/ + float r = (x + y + z) * R3; // Rotation, not skew + x = r - x; + y = r - y; + z = r - z; + } + break; + default: + break; + } + + return switch (mFractalType) { + default -> genNoiseSingle(mSeed, x, y, z); + case FBm -> genFractalFBm(x, y, z); + case Ridged -> genFractalRidged(x, y, z); + case PingPong -> genFractalPingPong(x, y, z); + }; + } + + /// + /// 2D warps the input position using current domain warp settings + /// + /// + /// Example usage with GetNoise + /// DomainWarp(coord) + /// noise = GetNoise(x, y) + /// + public void domainWarp(Vector2 coord) { + switch (mFractalType) { + default: + domainWarpSingle(coord); + break; + case DomainWarpProgressive: + domainWarpFractalProgressive(coord); + break; + case DomainWarpIndependent: + domainWarpFractalIndependent(coord); + break; + } + } + + /// + /// 3D warps the input position using current domain warp settings + /// + /// + /// Example usage with GetNoise + /// DomainWarp(coord) + /// noise = GetNoise(x, y, z) + /// + public void domainWarp(Vector3 coord) { + switch (mFractalType) { + default: + domainWarpSingle(coord); + break; + case DomainWarpProgressive: + domainWarpFractalProgressive(coord); + break; + case DomainWarpIndependent: + domainWarpFractalIndependent(coord); + break; + } + } + + private void CalculateFractalBounding() { + float gain = FastAbs(mGain); + float amp = gain; + float ampFractal = 1.0f; + for (int i = 1; i < mOctaves; i++) { + ampFractal += amp; + amp *= gain; + } + mFractalBounding = 1 / ampFractal; + } + + private float genNoiseSingle(int seed, /*FNLfloat*/ float x, /*FNLfloat*/ float y) { + return switch (mNoiseType) { + case OpenSimplex2 -> SingleSimplex(seed, x, y); + case OpenSimplex2S -> SingleOpenSimplex2S(seed, x, y); + case Cellular -> SingleCellular(seed, x, y); + case Perlin -> SinglePerlin(seed, x, y); + case ValueCubic -> SingleValueCubic(seed, x, y); + case Value -> SingleValue(seed, x, y); + }; + } + + private float genNoiseSingle(int seed, /*FNLfloat*/ float x, /*FNLfloat*/ float y, /*FNLfloat*/ float z) { + return switch (mNoiseType) { + case OpenSimplex2 -> SingleOpenSimplex2(seed, x, y, z); + case OpenSimplex2S -> SingleOpenSimplex2S(seed, x, y, z); + case Cellular -> SingleCellular(seed, x, y, z); + case Perlin -> SinglePerlin(seed, x, y, z); + case ValueCubic -> SingleValueCubic(seed, x, y, z); + case Value -> SingleValue(seed, x, y, z); + }; + } + + private void UpdateTransformType3D() { + switch (mRotationType3D) { + case ImproveXYPlanes: + mTransformType3D = TransformType3D.ImproveXYPlanes; + break; + case ImproveXZPlanes: + mTransformType3D = TransformType3D.ImproveXZPlanes; + break; + default: + switch (mNoiseType) { + case OpenSimplex2: + case OpenSimplex2S: + mTransformType3D = TransformType3D.DefaultOpenSimplex2; + break; + default: + mTransformType3D = TransformType3D.None; + break; + } + break; + } + } + + private void UpdateWarpTransformType3D() { + switch (mRotationType3D) { + case ImproveXYPlanes: + mWarpTransformType3D = TransformType3D.ImproveXYPlanes; + break; + case ImproveXZPlanes: + mWarpTransformType3D = TransformType3D.ImproveXZPlanes; + break; + default: + switch (mDomainWarpType) { + case OpenSimplex2: + case OpenSimplex2Reduced: + mWarpTransformType3D = TransformType3D.DefaultOpenSimplex2; + break; + default: + mWarpTransformType3D = TransformType3D.None; + break; + } + break; + } + } + + private float genFractalFBm(/*FNLfloat*/ float x, /*FNLfloat*/ float y) { + int seed = mSeed; + float sum = 0; + float amp = mFractalBounding; + + for (int i = 0; i < mOctaves; i++) { + float noise = genNoiseSingle(seed++, x, y); + sum += noise * amp; + amp *= Lerp(1.0f, FastMin(noise + 1, 2) * 0.5f, mWeightedStrength); + + x *= mLacunarity; + y *= mLacunarity; + amp *= mGain; + } + + return sum; + } + + private float genFractalFBm(/*FNLfloat*/ float x, /*FNLfloat*/ float y, /*FNLfloat*/ float z) { + int seed = mSeed; + float sum = 0; + float amp = mFractalBounding; + + for (int i = 0; i < mOctaves; i++) { + float noise = genNoiseSingle(seed++, x, y, z); + sum += noise * amp; + amp *= Lerp(1.0f, (noise + 1) * 0.5f, mWeightedStrength); + + x *= mLacunarity; + y *= mLacunarity; + z *= mLacunarity; + amp *= mGain; + } + + return sum; + } + + private float genFractalRidged(/*FNLfloat*/ float x, /*FNLfloat*/ float y) { + int seed = mSeed; + float sum = 0; + float amp = mFractalBounding; + + for (int i = 0; i < mOctaves; i++) { + float noise = FastAbs(genNoiseSingle(seed++, x, y)); + sum += (noise * -2 + 1) * amp; + amp *= Lerp(1.0f, 1 - noise, mWeightedStrength); + + x *= mLacunarity; + y *= mLacunarity; + amp *= mGain; + } + + return sum; + } + + + // Generic noise gen + + private float genFractalRidged(/*FNLfloat*/ float x, /*FNLfloat*/ float y, /*FNLfloat*/ float z) { + int seed = mSeed; + float sum = 0; + float amp = mFractalBounding; + + for (int i = 0; i < mOctaves; i++) { + float noise = FastAbs(genNoiseSingle(seed++, x, y, z)); + sum += (noise * -2 + 1) * amp; + amp *= Lerp(1.0f, 1 - noise, mWeightedStrength); + + x *= mLacunarity; + y *= mLacunarity; + z *= mLacunarity; + amp *= mGain; + } + + return sum; + } + + private float genFractalPingPong(/*FNLfloat*/ float x, /*FNLfloat*/ float y) { + int seed = mSeed; + float sum = 0; + float amp = mFractalBounding; + + for (int i = 0; i < mOctaves; i++) { + float noise = PingPong((genNoiseSingle(seed++, x, y) + 1) * mPingPongStrength); + sum += (noise - 0.5f) * 2 * amp; + amp *= Lerp(1.0f, noise, mWeightedStrength); + + x *= mLacunarity; + y *= mLacunarity; + amp *= mGain; + } + + return sum; + } + + + // Noise Coordinate Transforms (frequency, and possible skew or rotation) + + private float genFractalPingPong(/*FNLfloat*/ float x, /*FNLfloat*/ float y, /*FNLfloat*/ float z) { + int seed = mSeed; + float sum = 0; + float amp = mFractalBounding; + + for (int i = 0; i < mOctaves; i++) { + float noise = PingPong((genNoiseSingle(seed++, x, y, z) + 1) * mPingPongStrength); + sum += (noise - 0.5f) * 2 * amp; + amp *= Lerp(1.0f, noise, mWeightedStrength); + + x *= mLacunarity; + y *= mLacunarity; + z *= mLacunarity; + amp *= mGain; + } + + return sum; + } + + private float SingleSimplex(int seed, /*FNLfloat*/ float x, /*FNLfloat*/ float y) { + // 2D OpenSimplex2 case uses the same algorithm as ordinary Simplex. + + final float SQRT3 = 1.7320508075688772935274463415059f; + final float G2 = (3 - SQRT3) / 6; + + /* + * --- Skew moved to switch statements before fractal evaluation --- + * final FNLfloat F2 = 0.5f * (SQRT3 - 1); + * FNLfloat s = (x + y) * F2; + * x += s; y += s; + */ + + int i = FastFloor(x); + int j = FastFloor(y); + float xi = x - i; + float yi = y - j; + + float t = (xi + yi) * G2; + float x0 = xi - t; + float y0 = yi - t; + + i *= PrimeX; + j *= PrimeY; + + float n0, n1, n2; + + float a = 0.5f - x0 * x0 - y0 * y0; + if (a <= 0) n0 = 0; + else { + n0 = (a * a) * (a * a) * gradCoord(seed, i, j, x0, y0); + } + + float c = 2 * (1 - 2 * G2) * (1 / G2 - 2) * t + ((-2 * (1 - 2 * G2) * (1 - 2 * G2)) + a); + if (c <= 0) n2 = 0; + else { + float x2 = x0 + (2 * G2 - 1); + float y2 = y0 + (2 * G2 - 1); + n2 = (c * c) * (c * c) * gradCoord(seed, i + PrimeX, j + PrimeY, x2, y2); + } + + if (y0 > x0) { + float x1 = x0 + G2; + float y1 = y0 + (G2 - 1); + float b = 0.5f - x1 * x1 - y1 * y1; + if (b <= 0) n1 = 0; + else { + n1 = (b * b) * (b * b) * gradCoord(seed, i, j + PrimeY, x1, y1); + } + } else { + float x1 = x0 + (G2 - 1); + float y1 = y0 + G2; + float b = 0.5f - x1 * x1 - y1 * y1; + if (b <= 0) n1 = 0; + else { + n1 = (b * b) * (b * b) * gradCoord(seed, i + PrimeX, j, x1, y1); + } + } + + return (n0 + n1 + n2) * 99.83685446303647f; + } + + + // Fractal FBm + + private float SingleOpenSimplex2(int seed, /*FNLfloat*/ float x, /*FNLfloat*/ float y, /*FNLfloat*/ float z) { + // 3D OpenSimplex2 case uses two offset rotated cube grids. + + /* + * --- Rotation moved to switch statements before fractal evaluation --- + * final FNLfloat R3 = (FNLfloat)(2.0 / 3.0); + * FNLfloat r = (x + y + z) * R3; // Rotation, not skew + * x = r - x; y = r - y; z = r - z; + */ + + int i = FastRound(x); + int j = FastRound(y); + int k = FastRound(z); + float x0 = x - i; + float y0 = y - j; + float z0 = z - k; + + int xNSign = (int) (-1.0f - x0) | 1; + int yNSign = (int) (-1.0f - y0) | 1; + int zNSign = (int) (-1.0f - z0) | 1; + + float ax0 = xNSign * -x0; + float ay0 = yNSign * -y0; + float az0 = zNSign * -z0; + + i *= PrimeX; + j *= PrimeY; + k *= PrimeZ; + + float value = 0; + float a = (0.6f - x0 * x0) - (y0 * y0 + z0 * z0); + + for (int l = 0; ; l++) { + if (a > 0) { + value += (a * a) * (a * a) * gradCoord(seed, i, j, k, x0, y0, z0); + } + + if (ax0 >= ay0 && ax0 >= az0) { + float b = a + ax0 + ax0; + if (b > 1) { + b -= 1; + value += (b * b) * (b * b) * gradCoord(seed, i - xNSign * PrimeX, j, k, x0 + xNSign, y0, z0); + } + } else if (ay0 > ax0 && ay0 >= az0) { + float b = a + ay0 + ay0; + if (b > 1) { + b -= 1; + value += (b * b) * (b * b) * gradCoord(seed, i, j - yNSign * PrimeY, k, x0, y0 + yNSign, z0); + } + } else { + float b = a + az0 + az0; + if (b > 1) { + b -= 1; + value += (b * b) * (b * b) * gradCoord(seed, i, j, k - zNSign * PrimeZ, x0, y0, z0 + zNSign); + } + } + + if (l == 1) break; + + ax0 = 0.5f - ax0; + ay0 = 0.5f - ay0; + az0 = 0.5f - az0; + + x0 = xNSign * ax0; + y0 = yNSign * ay0; + z0 = zNSign * az0; + + a += (0.75f - ax0) - (ay0 + az0); + + i += (xNSign >> 1) & PrimeX; + j += (yNSign >> 1) & PrimeY; + k += (zNSign >> 1) & PrimeZ; + + xNSign = -xNSign; + yNSign = -yNSign; + zNSign = -zNSign; + + seed = ~seed; + } + + return value * 32.69428253173828125f; + } + + private float SingleOpenSimplex2S(int seed, /*FNLfloat*/ float x, /*FNLfloat*/ float y) { + // 2D OpenSimplex2S case is a modified 2D simplex noise. + + final /*FNLfloat*/ float SQRT3 = (/*FNLfloat*/ float) 1.7320508075688772935274463415059; + final /*FNLfloat*/ float G2 = (3 - SQRT3) / 6; + + /* + * --- Skew moved to TransformNoiseCoordinate method --- + * final FNLfloat F2 = 0.5f * (SQRT3 - 1); + * FNLfloat s = (x + y) * F2; + * x += s; y += s; + */ + + int i = FastFloor(x); + int j = FastFloor(y); + float xi = x - i; + float yi = y - j; + + i *= PrimeX; + j *= PrimeY; + int i1 = i + PrimeX; + int j1 = j + PrimeY; + + float t = (xi + yi) * G2; + float x0 = xi - t; + float y0 = yi - t; + + float a0 = (2.0f / 3.0f) - x0 * x0 - y0 * y0; + float value = (a0 * a0) * (a0 * a0) * gradCoord(seed, i, j, x0, y0); + + float a1 = 2 * (1 - 2 * G2) * (1 / G2 - 2) * t + ((-2 * (1 - 2 * G2) * (1 - 2 * G2)) + a0); + float x1 = x0 - (1 - 2 * G2); + float y1 = y0 - (1 - 2 * G2); + value += (a1 * a1) * (a1 * a1) * gradCoord(seed, i1, j1, x1, y1); + + // Nested conditionals were faster than compact bit logic/arithmetic. + float xmyi = xi - yi; + if (t > G2) { + if (xi + xmyi > 1) { + float x2 = x0 + (3 * G2 - 2); + float y2 = y0 + (3 * G2 - 1); + float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2; + if (a2 > 0) { + value += (a2 * a2) * (a2 * a2) * gradCoord(seed, i + (PrimeX << 1), j + PrimeY, x2, y2); + } + } else { + float x2 = x0 + G2; + float y2 = y0 + (G2 - 1); + float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2; + if (a2 > 0) { + value += (a2 * a2) * (a2 * a2) * gradCoord(seed, i, j + PrimeY, x2, y2); + } + } + + if (yi - xmyi > 1) { + float x3 = x0 + (3 * G2 - 1); + float y3 = y0 + (3 * G2 - 2); + float a3 = (2.0f / 3.0f) - x3 * x3 - y3 * y3; + if (a3 > 0) { + value += (a3 * a3) * (a3 * a3) * gradCoord(seed, i + PrimeX, j + (PrimeY << 1), x3, y3); + } + } else { + float x3 = x0 + (G2 - 1); + float y3 = y0 + G2; + float a3 = (2.0f / 3.0f) - x3 * x3 - y3 * y3; + if (a3 > 0) { + value += (a3 * a3) * (a3 * a3) * gradCoord(seed, i + PrimeX, j, x3, y3); + } + } + } else { + if (xi + xmyi < 0) { + float x2 = x0 + (1 - G2); + float y2 = y0 - G2; + float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2; + if (a2 > 0) { + value += (a2 * a2) * (a2 * a2) * gradCoord(seed, i - PrimeX, j, x2, y2); + } + } else { + float x2 = x0 + (G2 - 1); + float y2 = y0 + G2; + float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2; + if (a2 > 0) { + value += (a2 * a2) * (a2 * a2) * gradCoord(seed, i + PrimeX, j, x2, y2); + } + } + + if (yi < xmyi) { + float x2 = x0 - G2; + float y2 = y0 - (G2 - 1); + float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2; + if (a2 > 0) { + value += (a2 * a2) * (a2 * a2) * gradCoord(seed, i, j - PrimeY, x2, y2); + } + } else { + float x2 = x0 + G2; + float y2 = y0 + (G2 - 1); + float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2; + if (a2 > 0) { + value += (a2 * a2) * (a2 * a2) * gradCoord(seed, i, j + PrimeY, x2, y2); + } + } + } + + return value * 18.24196194486065f; + } + + + // Fractal Ridged + + private float SingleOpenSimplex2S(int seed, /*FNLfloat*/ float x, /*FNLfloat*/ float y, /*FNLfloat*/ float z) { + // 3D OpenSimplex2S case uses two offset rotated cube grids. + + /* + * --- Rotation moved to TransformNoiseCoordinate method --- + * final FNLfloat R3 = (FNLfloat)(2.0 / 3.0); + * FNLfloat r = (x + y + z) * R3; // Rotation, not skew + * x = r - x; y = r - y; z = r - z; + */ + + int i = FastFloor(x); + int j = FastFloor(y); + int k = FastFloor(z); + float xi = x - i; + float yi = y - j; + float zi = z - k; + + i *= PrimeX; + j *= PrimeY; + k *= PrimeZ; + int seed2 = seed + 1293373; + + int xNMask = (int) (-0.5f - xi); + int yNMask = (int) (-0.5f - yi); + int zNMask = (int) (-0.5f - zi); + + float x0 = xi + xNMask; + float y0 = yi + yNMask; + float z0 = zi + zNMask; + float a0 = 0.75f - x0 * x0 - y0 * y0 - z0 * z0; + float value = (a0 * a0) * (a0 * a0) * gradCoord(seed, + i + (xNMask & PrimeX), j + (yNMask & PrimeY), k + (zNMask & PrimeZ), x0, y0, z0); + + float x1 = xi - 0.5f; + float y1 = yi - 0.5f; + float z1 = zi - 0.5f; + float a1 = 0.75f - x1 * x1 - y1 * y1 - z1 * z1; + value += (a1 * a1) * (a1 * a1) * gradCoord(seed2, + i + PrimeX, j + PrimeY, k + PrimeZ, x1, y1, z1); + + float xAFlipMask0 = ((xNMask | 1) << 1) * x1; + float yAFlipMask0 = ((yNMask | 1) << 1) * y1; + float zAFlipMask0 = ((zNMask | 1) << 1) * z1; + float xAFlipMask1 = (-2 - (xNMask << 2)) * x1 - 1.0f; + float yAFlipMask1 = (-2 - (yNMask << 2)) * y1 - 1.0f; + float zAFlipMask1 = (-2 - (zNMask << 2)) * z1 - 1.0f; + + boolean skip5 = false; + float a2 = xAFlipMask0 + a0; + if (a2 > 0) { + float x2 = x0 - (xNMask | 1); + value += (a2 * a2) * (a2 * a2) * gradCoord(seed, + i + (~xNMask & PrimeX), j + (yNMask & PrimeY), k + (zNMask & PrimeZ), x2, y0, z0); + } else { + float a3 = yAFlipMask0 + zAFlipMask0 + a0; + if (a3 > 0) { + float y3 = y0 - (yNMask | 1); + float z3 = z0 - (zNMask | 1); + value += (a3 * a3) * (a3 * a3) * gradCoord(seed, + i + (xNMask & PrimeX), j + (~yNMask & PrimeY), k + (~zNMask & PrimeZ), x0, y3, z3); + } + + float a4 = xAFlipMask1 + a1; + if (a4 > 0) { + float x4 = (xNMask | 1) + x1; + value += (a4 * a4) * (a4 * a4) * gradCoord(seed2, + i + (xNMask & (PrimeX * 2)), j + PrimeY, k + PrimeZ, x4, y1, z1); + skip5 = true; + } + } + + boolean skip9 = false; + float a6 = yAFlipMask0 + a0; + if (a6 > 0) { + float y6 = y0 - (yNMask | 1); + value += (a6 * a6) * (a6 * a6) * gradCoord(seed, + i + (xNMask & PrimeX), j + (~yNMask & PrimeY), k + (zNMask & PrimeZ), x0, y6, z0); + } else { + float a7 = xAFlipMask0 + zAFlipMask0 + a0; + if (a7 > 0) { + float x7 = x0 - (xNMask | 1); + float z7 = z0 - (zNMask | 1); + value += (a7 * a7) * (a7 * a7) * gradCoord(seed, + i + (~xNMask & PrimeX), j + (yNMask & PrimeY), k + (~zNMask & PrimeZ), x7, y0, z7); + } + + float a8 = yAFlipMask1 + a1; + if (a8 > 0) { + float y8 = (yNMask | 1) + y1; + value += (a8 * a8) * (a8 * a8) * gradCoord(seed2, + i + PrimeX, j + (yNMask & (PrimeY << 1)), k + PrimeZ, x1, y8, z1); + skip9 = true; + } + } + + boolean skipD = false; + float aA = zAFlipMask0 + a0; + if (aA > 0) { + float zA = z0 - (zNMask | 1); + value += (aA * aA) * (aA * aA) * gradCoord(seed, + i + (xNMask & PrimeX), j + (yNMask & PrimeY), k + (~zNMask & PrimeZ), x0, y0, zA); + } else { + float aB = xAFlipMask0 + yAFlipMask0 + a0; + if (aB > 0) { + float xB = x0 - (xNMask | 1); + float yB = y0 - (yNMask | 1); + value += (aB * aB) * (aB * aB) * gradCoord(seed, + i + (~xNMask & PrimeX), j + (~yNMask & PrimeY), k + (zNMask & PrimeZ), xB, yB, z0); + } + + float aC = zAFlipMask1 + a1; + if (aC > 0) { + float zC = (zNMask | 1) + z1; + value += (aC * aC) * (aC * aC) * gradCoord(seed2, + i + PrimeX, j + PrimeY, k + (zNMask & (PrimeZ << 1)), x1, y1, zC); + skipD = true; + } + } + + if (!skip5) { + float a5 = yAFlipMask1 + zAFlipMask1 + a1; + if (a5 > 0) { + float y5 = (yNMask | 1) + y1; + float z5 = (zNMask | 1) + z1; + value += (a5 * a5) * (a5 * a5) * gradCoord(seed2, + i + PrimeX, j + (yNMask & (PrimeY << 1)), k + (zNMask & (PrimeZ << 1)), x1, y5, z5); + } + } + + if (!skip9) { + float a9 = xAFlipMask1 + zAFlipMask1 + a1; + if (a9 > 0) { + float x9 = (xNMask | 1) + x1; + float z9 = (zNMask | 1) + z1; + value += (a9 * a9) * (a9 * a9) * gradCoord(seed2, + i + (xNMask & (PrimeX * 2)), j + PrimeY, k + (zNMask & (PrimeZ << 1)), x9, y1, z9); + } + } + + if (!skipD) { + float aD = xAFlipMask1 + yAFlipMask1 + a1; + if (aD > 0) { + float xD = (xNMask | 1) + x1; + float yD = (yNMask | 1) + y1; + value += (aD * aD) * (aD * aD) * gradCoord(seed2, + i + (xNMask & (PrimeX << 1)), j + (yNMask & (PrimeY << 1)), k + PrimeZ, xD, yD, z1); + } + } + + return value * 9.046026385208288f; + } + + private float SingleCellular(int seed, /*FNLfloat*/ float x, /*FNLfloat*/ float y) { + int xr = FastRound(x); + int yr = FastRound(y); + + float distance0 = Float.MAX_VALUE; + float distance1 = Float.MAX_VALUE; + int closestHash = 0; + + float cellularJitter = 0.43701595f * mCellularJitterModifier; + + int xPrimed = (xr - 1) * PrimeX; + int yPrimedBase = (yr - 1) * PrimeY; + + switch (mCellularDistanceFunction) { + default: + case Euclidean: + case EuclideanSq: + for (int xi = xr - 1; xi <= xr + 1; xi++) { + int yPrimed = yPrimedBase; + + for (int yi = yr - 1; yi <= yr + 1; yi++) { + int hash = Hash(seed, xPrimed, yPrimed); + int idx = hash & (255 << 1); + + float vecX = (xi - x) + RandVecs2D[idx] * cellularJitter; + float vecY = (yi - y) + RandVecs2D[idx | 1] * cellularJitter; + + float newDistance = vecX * vecX + vecY * vecY; + + distance1 = FastMax(FastMin(distance1, newDistance), distance0); + if (newDistance < distance0) { + distance0 = newDistance; + closestHash = hash; + } + yPrimed += PrimeY; + } + xPrimed += PrimeX; + } + break; + case Manhattan: + for (int xi = xr - 1; xi <= xr + 1; xi++) { + int yPrimed = yPrimedBase; + + for (int yi = yr - 1; yi <= yr + 1; yi++) { + int hash = Hash(seed, xPrimed, yPrimed); + int idx = hash & (255 << 1); + + float vecX = (xi - x) + RandVecs2D[idx] * cellularJitter; + float vecY = (yi - y) + RandVecs2D[idx | 1] * cellularJitter; + + float newDistance = FastAbs(vecX) + FastAbs(vecY); + + distance1 = FastMax(FastMin(distance1, newDistance), distance0); + if (newDistance < distance0) { + distance0 = newDistance; + closestHash = hash; + } + yPrimed += PrimeY; + } + xPrimed += PrimeX; + } + break; + case Hybrid: + for (int xi = xr - 1; xi <= xr + 1; xi++) { + int yPrimed = yPrimedBase; + + for (int yi = yr - 1; yi <= yr + 1; yi++) { + int hash = Hash(seed, xPrimed, yPrimed); + int idx = hash & (255 << 1); + + float vecX = (xi - x) + RandVecs2D[idx] * cellularJitter; + float vecY = (yi - y) + RandVecs2D[idx | 1] * cellularJitter; + + float newDistance = (FastAbs(vecX) + FastAbs(vecY)) + (vecX * vecX + vecY * vecY); + + distance1 = FastMax(FastMin(distance1, newDistance), distance0); + if (newDistance < distance0) { + distance0 = newDistance; + closestHash = hash; + } + yPrimed += PrimeY; + } + xPrimed += PrimeX; + } + break; + } + + if (mCellularDistanceFunction == CellularDistanceFunction.Euclidean && mCellularReturnType != CellularReturnType.CellValue) { + distance0 = FastSqrt(distance0); + + if (mCellularReturnType != CellularReturnType.Distance) { + distance1 = FastSqrt(distance1); + } + } + + return switch (mCellularReturnType) { + case CellValue -> closestHash * (1 / 2147483648.0f); + case Distance -> distance0 - 1; + case Distance2 -> distance1 - 1; + case Distance2Add -> (distance1 + distance0) * 0.5f - 1; + case Distance2Sub -> distance1 - distance0 - 1; + case Distance2Mul -> distance1 * distance0 * 0.5f - 1; + case Distance2Div -> distance0 / distance1 - 1; + }; + } + + + // Fractal PingPong + + private float SingleCellular(int seed, /*FNLfloat*/ float x, /*FNLfloat*/ float y, /*FNLfloat*/ float z) { + int xr = FastRound(x); + int yr = FastRound(y); + int zr = FastRound(z); + + float distance0 = Float.MAX_VALUE; + float distance1 = Float.MAX_VALUE; + int closestHash = 0; + + float cellularJitter = 0.39614353f * mCellularJitterModifier; + + int xPrimed = (xr - 1) * PrimeX; + int yPrimedBase = (yr - 1) * PrimeY; + int zPrimedBase = (zr - 1) * PrimeZ; + + switch (mCellularDistanceFunction) { + case Euclidean: + case EuclideanSq: + for (int xi = xr - 1; xi <= xr + 1; xi++) { + int yPrimed = yPrimedBase; + + for (int yi = yr - 1; yi <= yr + 1; yi++) { + int zPrimed = zPrimedBase; + + for (int zi = zr - 1; zi <= zr + 1; zi++) { + int hash = Hash(seed, xPrimed, yPrimed, zPrimed); + int idx = hash & (255 << 2); + + float vecX = (xi - x) + RandVecs3D[idx] * cellularJitter; + float vecY = (yi - y) + RandVecs3D[idx | 1] * cellularJitter; + float vecZ = (zi - z) + RandVecs3D[idx | 2] * cellularJitter; + + float newDistance = vecX * vecX + vecY * vecY + vecZ * vecZ; + + distance1 = FastMax(FastMin(distance1, newDistance), distance0); + if (newDistance < distance0) { + distance0 = newDistance; + closestHash = hash; + } + zPrimed += PrimeZ; + } + yPrimed += PrimeY; + } + xPrimed += PrimeX; + } + break; + case Manhattan: + for (int xi = xr - 1; xi <= xr + 1; xi++) { + int yPrimed = yPrimedBase; + + for (int yi = yr - 1; yi <= yr + 1; yi++) { + int zPrimed = zPrimedBase; + + for (int zi = zr - 1; zi <= zr + 1; zi++) { + int hash = Hash(seed, xPrimed, yPrimed, zPrimed); + int idx = hash & (255 << 2); + + float vecX = (xi - x) + RandVecs3D[idx] * cellularJitter; + float vecY = (yi - y) + RandVecs3D[idx | 1] * cellularJitter; + float vecZ = (zi - z) + RandVecs3D[idx | 2] * cellularJitter; + + float newDistance = FastAbs(vecX) + FastAbs(vecY) + FastAbs(vecZ); + + distance1 = FastMax(FastMin(distance1, newDistance), distance0); + if (newDistance < distance0) { + distance0 = newDistance; + closestHash = hash; + } + zPrimed += PrimeZ; + } + yPrimed += PrimeY; + } + xPrimed += PrimeX; + } + break; + case Hybrid: + for (int xi = xr - 1; xi <= xr + 1; xi++) { + int yPrimed = yPrimedBase; + + for (int yi = yr - 1; yi <= yr + 1; yi++) { + int zPrimed = zPrimedBase; + + for (int zi = zr - 1; zi <= zr + 1; zi++) { + int hash = Hash(seed, xPrimed, yPrimed, zPrimed); + int idx = hash & (255 << 2); + + float vecX = (xi - x) + RandVecs3D[idx] * cellularJitter; + float vecY = (yi - y) + RandVecs3D[idx | 1] * cellularJitter; + float vecZ = (zi - z) + RandVecs3D[idx | 2] * cellularJitter; + + float newDistance = (FastAbs(vecX) + FastAbs(vecY) + FastAbs(vecZ)) + (vecX * vecX + vecY * vecY + vecZ * vecZ); + + distance1 = FastMax(FastMin(distance1, newDistance), distance0); + if (newDistance < distance0) { + distance0 = newDistance; + closestHash = hash; + } + zPrimed += PrimeZ; + } + yPrimed += PrimeY; + } + xPrimed += PrimeX; + } + break; + default: + break; + } + + if (mCellularDistanceFunction == CellularDistanceFunction.Euclidean && mCellularReturnType != CellularReturnType.CellValue) { + distance0 = FastSqrt(distance0); + + if (mCellularReturnType != CellularReturnType.Distance) { + distance1 = FastSqrt(distance1); + } + } + + return switch (mCellularReturnType) { + case CellValue -> closestHash * (1 / 2147483648.0f); + case Distance -> distance0 - 1; + case Distance2 -> distance1 - 1; + case Distance2Add -> (distance1 + distance0) * 0.5f - 1; + case Distance2Sub -> distance1 - distance0 - 1; + case Distance2Mul -> distance1 * distance0 * 0.5f - 1; + case Distance2Div -> distance0 / distance1 - 1; + }; + } + + private float SinglePerlin(int seed, /*FNLfloat*/ float x, /*FNLfloat*/ float y) { + int x0 = FastFloor(x); + int y0 = FastFloor(y); + + float xd0 = x - x0; + float yd0 = y - y0; + float xd1 = xd0 - 1; + float yd1 = yd0 - 1; + + float xs = InterpQuintic(xd0); + float ys = InterpQuintic(yd0); + + x0 *= PrimeX; + y0 *= PrimeY; + int x1 = x0 + PrimeX; + int y1 = y0 + PrimeY; + + float xf0 = Lerp(gradCoord(seed, x0, y0, xd0, yd0), gradCoord(seed, x1, y0, xd1, yd0), xs); + float xf1 = Lerp(gradCoord(seed, x0, y1, xd0, yd1), gradCoord(seed, x1, y1, xd1, yd1), xs); + + return Lerp(xf0, xf1, ys) * 1.4247691104677813f; + } + + + // Simplex/OpenSimplex2 Noise + + private float SinglePerlin(int seed, /*FNLfloat*/ float x, /*FNLfloat*/ float y, /*FNLfloat*/ float z) { + int x0 = FastFloor(x); + int y0 = FastFloor(y); + int z0 = FastFloor(z); + + float xd0 = x - x0; + float yd0 = y - y0; + float zd0 = z - z0; + float xd1 = xd0 - 1; + float yd1 = yd0 - 1; + float zd1 = zd0 - 1; + + float xs = InterpQuintic(xd0); + float ys = InterpQuintic(yd0); + float zs = InterpQuintic(zd0); + + x0 *= PrimeX; + y0 *= PrimeY; + z0 *= PrimeZ; + int x1 = x0 + PrimeX; + int y1 = y0 + PrimeY; + int z1 = z0 + PrimeZ; + + float xf00 = Lerp(gradCoord(seed, x0, y0, z0, xd0, yd0, zd0), gradCoord(seed, x1, y0, z0, xd1, yd0, zd0), xs); + float xf10 = Lerp(gradCoord(seed, x0, y1, z0, xd0, yd1, zd0), gradCoord(seed, x1, y1, z0, xd1, yd1, zd0), xs); + float xf01 = Lerp(gradCoord(seed, x0, y0, z1, xd0, yd0, zd1), gradCoord(seed, x1, y0, z1, xd1, yd0, zd1), xs); + float xf11 = Lerp(gradCoord(seed, x0, y1, z1, xd0, yd1, zd1), gradCoord(seed, x1, y1, z1, xd1, yd1, zd1), xs); + + float yf0 = Lerp(xf00, xf10, ys); + float yf1 = Lerp(xf01, xf11, ys); + + return Lerp(yf0, yf1, zs) * 0.964921414852142333984375f; + } + + private float SingleValueCubic(int seed, /*FNLfloat*/ float x, /*FNLfloat*/ float y) { + int x1 = FastFloor(x); + int y1 = FastFloor(y); + + float xs = x - x1; + float ys = y - y1; + + x1 *= PrimeX; + y1 *= PrimeY; + int x0 = x1 - PrimeX; + int y0 = y1 - PrimeY; + int x2 = x1 + PrimeX; + int y2 = y1 + PrimeY; + int x3 = x1 + (PrimeX << 1); + int y3 = y1 + (PrimeY << 1); + + return CubicLerp( + CubicLerp(ValCoord(seed, x0, y0), ValCoord(seed, x1, y0), ValCoord(seed, x2, y0), ValCoord(seed, x3, y0), + xs), + CubicLerp(ValCoord(seed, x0, y1), ValCoord(seed, x1, y1), ValCoord(seed, x2, y1), ValCoord(seed, x3, y1), + xs), + CubicLerp(ValCoord(seed, x0, y2), ValCoord(seed, x1, y2), ValCoord(seed, x2, y2), ValCoord(seed, x3, y2), + xs), + CubicLerp(ValCoord(seed, x0, y3), ValCoord(seed, x1, y3), ValCoord(seed, x2, y3), ValCoord(seed, x3, y3), + xs), + ys) * (1 / (1.5f * 1.5f)); + } + + + // OpenSimplex2S Noise + + private float SingleValueCubic(int seed, /*FNLfloat*/ float x, /*FNLfloat*/ float y, /*FNLfloat*/ float z) { + int x1 = FastFloor(x); + int y1 = FastFloor(y); + int z1 = FastFloor(z); + + float xs = x - x1; + float ys = y - y1; + float zs = z - z1; + + x1 *= PrimeX; + y1 *= PrimeY; + z1 *= PrimeZ; + + int x0 = x1 - PrimeX; + int y0 = y1 - PrimeY; + int z0 = z1 - PrimeZ; + int x2 = x1 + PrimeX; + int y2 = y1 + PrimeY; + int z2 = z1 + PrimeZ; + int x3 = x1 + (PrimeX << 1); + int y3 = y1 + (PrimeY << 1); + int z3 = z1 + (PrimeZ << 1); + + + return CubicLerp( + CubicLerp( + CubicLerp(ValCoord(seed, x0, y0, z0), ValCoord(seed, x1, y0, z0), ValCoord(seed, x2, y0, z0), ValCoord(seed, x3, y0, z0), xs), + CubicLerp(ValCoord(seed, x0, y1, z0), ValCoord(seed, x1, y1, z0), ValCoord(seed, x2, y1, z0), ValCoord(seed, x3, y1, z0), xs), + CubicLerp(ValCoord(seed, x0, y2, z0), ValCoord(seed, x1, y2, z0), ValCoord(seed, x2, y2, z0), ValCoord(seed, x3, y2, z0), xs), + CubicLerp(ValCoord(seed, x0, y3, z0), ValCoord(seed, x1, y3, z0), ValCoord(seed, x2, y3, z0), ValCoord(seed, x3, y3, z0), xs), + ys), + CubicLerp( + CubicLerp(ValCoord(seed, x0, y0, z1), ValCoord(seed, x1, y0, z1), ValCoord(seed, x2, y0, z1), ValCoord(seed, x3, y0, z1), xs), + CubicLerp(ValCoord(seed, x0, y1, z1), ValCoord(seed, x1, y1, z1), ValCoord(seed, x2, y1, z1), ValCoord(seed, x3, y1, z1), xs), + CubicLerp(ValCoord(seed, x0, y2, z1), ValCoord(seed, x1, y2, z1), ValCoord(seed, x2, y2, z1), ValCoord(seed, x3, y2, z1), xs), + CubicLerp(ValCoord(seed, x0, y3, z1), ValCoord(seed, x1, y3, z1), ValCoord(seed, x2, y3, z1), ValCoord(seed, x3, y3, z1), xs), + ys), + CubicLerp( + CubicLerp(ValCoord(seed, x0, y0, z2), ValCoord(seed, x1, y0, z2), ValCoord(seed, x2, y0, z2), ValCoord(seed, x3, y0, z2), xs), + CubicLerp(ValCoord(seed, x0, y1, z2), ValCoord(seed, x1, y1, z2), ValCoord(seed, x2, y1, z2), ValCoord(seed, x3, y1, z2), xs), + CubicLerp(ValCoord(seed, x0, y2, z2), ValCoord(seed, x1, y2, z2), ValCoord(seed, x2, y2, z2), ValCoord(seed, x3, y2, z2), xs), + CubicLerp(ValCoord(seed, x0, y3, z2), ValCoord(seed, x1, y3, z2), ValCoord(seed, x2, y3, z2), ValCoord(seed, x3, y3, z2), xs), + ys), + CubicLerp( + CubicLerp(ValCoord(seed, x0, y0, z3), ValCoord(seed, x1, y0, z3), ValCoord(seed, x2, y0, z3), ValCoord(seed, x3, y0, z3), xs), + CubicLerp(ValCoord(seed, x0, y1, z3), ValCoord(seed, x1, y1, z3), ValCoord(seed, x2, y1, z3), ValCoord(seed, x3, y1, z3), xs), + CubicLerp(ValCoord(seed, x0, y2, z3), ValCoord(seed, x1, y2, z3), ValCoord(seed, x2, y2, z3), ValCoord(seed, x3, y2, z3), xs), + CubicLerp(ValCoord(seed, x0, y3, z3), ValCoord(seed, x1, y3, z3), ValCoord(seed, x2, y3, z3), ValCoord(seed, x3, y3, z3), xs), + ys), + zs) * (1 / (1.5f * 1.5f * 1.5f)); + } + + private float SingleValue(int seed, /*FNLfloat*/ float x, /*FNLfloat*/ float y) { + int x0 = FastFloor(x); + int y0 = FastFloor(y); + + float xs = InterpHermite(x - x0); + float ys = InterpHermite(y - y0); + + x0 *= PrimeX; + y0 *= PrimeY; + int x1 = x0 + PrimeX; + int y1 = y0 + PrimeY; + + float xf0 = Lerp(ValCoord(seed, x0, y0), ValCoord(seed, x1, y0), xs); + float xf1 = Lerp(ValCoord(seed, x0, y1), ValCoord(seed, x1, y1), xs); + + return Lerp(xf0, xf1, ys); + } + + + // Cellular Noise + + private float SingleValue(int seed, /*FNLfloat*/ float x, /*FNLfloat*/ float y, /*FNLfloat*/ float z) { + int x0 = FastFloor(x); + int y0 = FastFloor(y); + int z0 = FastFloor(z); + + float xs = InterpHermite(x - x0); + float ys = InterpHermite(y - y0); + float zs = InterpHermite(z - z0); + + x0 *= PrimeX; + y0 *= PrimeY; + z0 *= PrimeZ; + int x1 = x0 + PrimeX; + int y1 = y0 + PrimeY; + int z1 = z0 + PrimeZ; + + float xf00 = Lerp(ValCoord(seed, x0, y0, z0), ValCoord(seed, x1, y0, z0), xs); + float xf10 = Lerp(ValCoord(seed, x0, y1, z0), ValCoord(seed, x1, y1, z0), xs); + float xf01 = Lerp(ValCoord(seed, x0, y0, z1), ValCoord(seed, x1, y0, z1), xs); + float xf11 = Lerp(ValCoord(seed, x0, y1, z1), ValCoord(seed, x1, y1, z1), xs); + + float yf0 = Lerp(xf00, xf10, ys); + float yf1 = Lerp(xf01, xf11, ys); + + return Lerp(yf0, yf1, zs); + } + + private void doSingleDomainWarp(int seed, float amp, float freq, /*FNLfloat*/ float x, /*FNLfloat*/ float y, Vector2 coord) { + switch (mDomainWarpType) { + case OpenSimplex2: + singleDomainWarpSimplexGradient(seed, amp * 38.283687591552734375f, freq, x, y, coord, false); + break; + case OpenSimplex2Reduced: + singleDomainWarpSimplexGradient(seed, amp * 16.0f, freq, x, y, coord, true); + break; + case BasicGrid: + singleDomainWarpBasicGrid(seed, amp, freq, x, y, coord); + break; + } + } + + + // Perlin Noise + + private void doSingleDomainWarp(int seed, float amp, float freq, /*FNLfloat*/ float x, /*FNLfloat*/ float y, /*FNLfloat*/ float z, Vector3 coord) { + switch (mDomainWarpType) { + case OpenSimplex2: + singleDomainWarpOpenSimplex2Gradient(seed, amp * 32.69428253173828125f, freq, x, y, z, coord, false); + break; + case OpenSimplex2Reduced: + singleDomainWarpOpenSimplex2Gradient(seed, amp * 7.71604938271605f, freq, x, y, z, coord, true); + break; + case BasicGrid: + singleDomainWarpBasicGrid(seed, amp, freq, x, y, z, coord); + break; + } + } + + private void domainWarpSingle(Vector2 coord) { + int seed = mSeed; + float amp = mDomainWarpAmp * mFractalBounding; + float freq = mFrequency; + + /*FNLfloat*/ + float xs = coord.x; + /*FNLfloat*/ + float ys = coord.y; + switch (mDomainWarpType) { + case OpenSimplex2: + case OpenSimplex2Reduced: { + final /*FNLfloat*/ float SQRT3 = (/*FNLfloat*/ float) 1.7320508075688772935274463415059; + final /*FNLfloat*/ float F2 = 0.5f * (SQRT3 - 1); + /*FNLfloat*/ + float t = (xs + ys) * F2; + xs += t; + ys += t; + } + break; + default: + break; + } + + doSingleDomainWarp(seed, amp, freq, xs, ys, coord); + } + + + // Value Cubic Noise + + private void domainWarpSingle(Vector3 coord) { + int seed = mSeed; + float amp = mDomainWarpAmp * mFractalBounding; + float freq = mFrequency; + + /*FNLfloat*/ + float xs = coord.x; + /*FNLfloat*/ + float ys = coord.y; + /*FNLfloat*/ + float zs = coord.z; + switch (mWarpTransformType3D) { + case ImproveXYPlanes: { + /*FNLfloat*/ + float xy = xs + ys; + /*FNLfloat*/ + float s2 = xy * -(/*FNLfloat*/ float) 0.211324865405187; + zs *= (/*FNLfloat*/ float) 0.577350269189626; + xs += s2 - zs; + ys = ys + s2 - zs; + zs += xy * (/*FNLfloat*/ float) 0.577350269189626; + } + break; + case ImproveXZPlanes: { + /*FNLfloat*/ + float xz = xs + zs; + /*FNLfloat*/ + float s2 = xz * -(/*FNLfloat*/ float) 0.211324865405187; + ys *= (/*FNLfloat*/ float) 0.577350269189626; + xs += s2 - ys; + zs += s2 - ys; + ys += xz * (/*FNLfloat*/ float) 0.577350269189626; + } + break; + case DefaultOpenSimplex2: { + final /*FNLfloat*/ float R3 = (/*FNLfloat*/ float) (2.0 / 3.0); + /*FNLfloat*/ + float r = (xs + ys + zs) * R3; // Rotation, not skew + xs = r - xs; + ys = r - ys; + zs = r - zs; + } + break; + default: + break; + } + + doSingleDomainWarp(seed, amp, freq, xs, ys, zs, coord); + } + + private void domainWarpFractalProgressive(Vector2 coord) { + int seed = mSeed; + float amp = mDomainWarpAmp * mFractalBounding; + float freq = mFrequency; + + for (int i = 0; i < mOctaves; i++) { + /*FNLfloat*/ + float xs = coord.x; + /*FNLfloat*/ + float ys = coord.y; + switch (mDomainWarpType) { + case OpenSimplex2: + case OpenSimplex2Reduced: { + final /*FNLfloat*/ float SQRT3 = (/*FNLfloat*/ float) 1.7320508075688772935274463415059; + final /*FNLfloat*/ float F2 = 0.5f * (SQRT3 - 1); + /*FNLfloat*/ + float t = (xs + ys) * F2; + xs += t; + ys += t; + } + break; + default: + break; + } + + doSingleDomainWarp(seed, amp, freq, xs, ys, coord); + + seed++; + amp *= mGain; + freq *= mLacunarity; + } + } + + + // Value Noise + + private void domainWarpFractalProgressive(Vector3 coord) { + int seed = mSeed; + float amp = mDomainWarpAmp * mFractalBounding; + float freq = mFrequency; + + for (int i = 0; i < mOctaves; i++) { + /*FNLfloat*/ + float xs = coord.x; + /*FNLfloat*/ + float ys = coord.y; + /*FNLfloat*/ + float zs = coord.z; + switch (mWarpTransformType3D) { + case ImproveXYPlanes: { + /*FNLfloat*/ + float xy = xs + ys; + /*FNLfloat*/ + float s2 = xy * -(/*FNLfloat*/ float) 0.211324865405187; + zs *= (/*FNLfloat*/ float) 0.577350269189626; + xs += s2 - zs; + ys = ys + s2 - zs; + zs += xy * (/*FNLfloat*/ float) 0.577350269189626; + } + break; + case ImproveXZPlanes: { + /*FNLfloat*/ + float xz = xs + zs; + /*FNLfloat*/ + float s2 = xz * -(/*FNLfloat*/ float) 0.211324865405187; + ys *= (/*FNLfloat*/ float) 0.577350269189626; + xs += s2 - ys; + zs += s2 - ys; + ys += xz * (/*FNLfloat*/ float) 0.577350269189626; + } + break; + case DefaultOpenSimplex2: { + final /*FNLfloat*/ float R3 = (/*FNLfloat*/ float) (2.0 / 3.0); + /*FNLfloat*/ + float r = (xs + ys + zs) * R3; // Rotation, not skew + xs = r - xs; + ys = r - ys; + zs = r - zs; + } + break; + default: + break; + } + + doSingleDomainWarp(seed, amp, freq, xs, ys, zs, coord); + + seed++; + amp *= mGain; + freq *= mLacunarity; + } + } + + // Domain Warp Fractal Independant + private void domainWarpFractalIndependent(Vector2 coord) { + /*FNLfloat*/ + float xs = coord.x; + /*FNLfloat*/ + float ys = coord.y; + switch (mDomainWarpType) { + case OpenSimplex2: + case OpenSimplex2Reduced: { + final /*FNLfloat*/ float SQRT3 = (/*FNLfloat*/ float) 1.7320508075688772935274463415059; + final /*FNLfloat*/ float F2 = 0.5f * (SQRT3 - 1); + /*FNLfloat*/ + float t = (xs + ys) * F2; + xs += t; + ys += t; + } + break; + default: + break; + } + + int seed = mSeed; + float amp = mDomainWarpAmp * mFractalBounding; + float freq = mFrequency; + + for (int i = 0; i < mOctaves; i++) { + doSingleDomainWarp(seed, amp, freq, xs, ys, coord); + + seed++; + amp *= mGain; + freq *= mLacunarity; + } + } + + + // Domain Warp + + private void domainWarpFractalIndependent(Vector3 coord) { + /*FNLfloat*/ + float xs = coord.x; + /*FNLfloat*/ + float ys = coord.y; + /*FNLfloat*/ + float zs = coord.z; + switch (mWarpTransformType3D) { + case ImproveXYPlanes: { + /*FNLfloat*/ + float xy = xs + ys; + /*FNLfloat*/ + float s2 = xy * -(/*FNLfloat*/ float) 0.211324865405187; + zs *= (/*FNLfloat*/ float) 0.577350269189626; + xs += s2 - zs; + ys = ys + s2 - zs; + zs += xy * (/*FNLfloat*/ float) 0.577350269189626; + } + break; + case ImproveXZPlanes: { + /*FNLfloat*/ + float xz = xs + zs; + /*FNLfloat*/ + float s2 = xz * -(/*FNLfloat*/ float) 0.211324865405187; + ys *= (/*FNLfloat*/ float) 0.577350269189626; + xs += s2 - ys; + zs += s2 - ys; + ys += xz * (/*FNLfloat*/ float) 0.577350269189626; + } + break; + case DefaultOpenSimplex2: { + final /*FNLfloat*/ float R3 = (/*FNLfloat*/ float) (2.0 / 3.0); + /*FNLfloat*/ + float r = (xs + ys + zs) * R3; // Rotation, not skew + xs = r - xs; + ys = r - ys; + zs = r - zs; + } + break; + default: + break; + } + + int seed = mSeed; + float amp = mDomainWarpAmp * mFractalBounding; + float freq = mFrequency; + + for (int i = 0; i < mOctaves; i++) { + doSingleDomainWarp(seed, amp, freq, xs, ys, zs, coord); + + seed++; + amp *= mGain; + freq *= mLacunarity; + } + } + + private void singleDomainWarpBasicGrid(int seed, float warpAmp, float frequency, /*FNLfloat*/ float x, /*FNLfloat*/ float y, Vector2 coord) { + /*FNLfloat*/ + float xf = x * frequency; + /*FNLfloat*/ + float yf = y * frequency; + + int x0 = FastFloor(xf); + int y0 = FastFloor(yf); + + float xs = InterpHermite(xf - x0); + float ys = InterpHermite(yf - y0); + + x0 *= PrimeX; + y0 *= PrimeY; + int x1 = x0 + PrimeX; + int y1 = y0 + PrimeY; + + int hash0 = Hash(seed, x0, y0) & (255 << 1); + int hash1 = Hash(seed, x1, y0) & (255 << 1); + + float lx0x = Lerp(RandVecs2D[hash0], RandVecs2D[hash1], xs); + float ly0x = Lerp(RandVecs2D[hash0 | 1], RandVecs2D[hash1 | 1], xs); + + hash0 = Hash(seed, x0, y1) & (255 << 1); + hash1 = Hash(seed, x1, y1) & (255 << 1); + + float lx1x = Lerp(RandVecs2D[hash0], RandVecs2D[hash1], xs); + float ly1x = Lerp(RandVecs2D[hash0 | 1], RandVecs2D[hash1 | 1], xs); + + coord.x += Lerp(lx0x, lx1x, ys) * warpAmp; + coord.y += Lerp(ly0x, ly1x, ys) * warpAmp; + } + + + // Domain Warp Single Wrapper + + private void singleDomainWarpBasicGrid(int seed, float warpAmp, float frequency, /*FNLfloat*/ float x, /*FNLfloat*/ float y, /*FNLfloat*/ float z, Vector3 coord) { + /*FNLfloat*/ + float xf = x * frequency; + /*FNLfloat*/ + float yf = y * frequency; + /*FNLfloat*/ + float zf = z * frequency; + + int x0 = FastFloor(xf); + int y0 = FastFloor(yf); + int z0 = FastFloor(zf); + + float xs = InterpHermite(xf - x0); + float ys = InterpHermite(yf - y0); + float zs = InterpHermite(zf - z0); + + x0 *= PrimeX; + y0 *= PrimeY; + z0 *= PrimeZ; + int x1 = x0 + PrimeX; + int y1 = y0 + PrimeY; + int z1 = z0 + PrimeZ; + + int hash0 = Hash(seed, x0, y0, z0) & (255 << 2); + int hash1 = Hash(seed, x1, y0, z0) & (255 << 2); + + float lx0x = Lerp(RandVecs3D[hash0], RandVecs3D[hash1], xs); + float ly0x = Lerp(RandVecs3D[hash0 | 1], RandVecs3D[hash1 | 1], xs); + float lz0x = Lerp(RandVecs3D[hash0 | 2], RandVecs3D[hash1 | 2], xs); + + hash0 = Hash(seed, x0, y1, z0) & (255 << 2); + hash1 = Hash(seed, x1, y1, z0) & (255 << 2); + + float lx1x = Lerp(RandVecs3D[hash0], RandVecs3D[hash1], xs); + float ly1x = Lerp(RandVecs3D[hash0 | 1], RandVecs3D[hash1 | 1], xs); + float lz1x = Lerp(RandVecs3D[hash0 | 2], RandVecs3D[hash1 | 2], xs); + + float lx0y = Lerp(lx0x, lx1x, ys); + float ly0y = Lerp(ly0x, ly1x, ys); + float lz0y = Lerp(lz0x, lz1x, ys); + + hash0 = Hash(seed, x0, y0, z1) & (255 << 2); + hash1 = Hash(seed, x1, y0, z1) & (255 << 2); + + lx0x = Lerp(RandVecs3D[hash0], RandVecs3D[hash1], xs); + ly0x = Lerp(RandVecs3D[hash0 | 1], RandVecs3D[hash1 | 1], xs); + lz0x = Lerp(RandVecs3D[hash0 | 2], RandVecs3D[hash1 | 2], xs); + + hash0 = Hash(seed, x0, y1, z1) & (255 << 2); + hash1 = Hash(seed, x1, y1, z1) & (255 << 2); + + lx1x = Lerp(RandVecs3D[hash0], RandVecs3D[hash1], xs); + ly1x = Lerp(RandVecs3D[hash0 | 1], RandVecs3D[hash1 | 1], xs); + lz1x = Lerp(RandVecs3D[hash0 | 2], RandVecs3D[hash1 | 2], xs); + + coord.x += Lerp(lx0y, Lerp(lx0x, lx1x, ys), zs) * warpAmp; + coord.y += Lerp(ly0y, Lerp(ly0x, ly1x, ys), zs) * warpAmp; + coord.z += Lerp(lz0y, Lerp(lz0x, lz1x, ys), zs) * warpAmp; + } + + // Domain Warp Simplex/OpenSimplex2 + private void singleDomainWarpSimplexGradient(int seed, float warpAmp, float frequency, /*FNLfloat*/ float x, /*FNLfloat*/ float y, Vector2 coord, boolean outGradOnly) { + final float SQRT3 = 1.7320508075688772935274463415059f; + final float G2 = (3 - SQRT3) / 6; + + x *= frequency; + y *= frequency; + + /* + * --- Skew moved to switch statements before fractal evaluation --- + * final FNLfloat F2 = 0.5f * (SQRT3 - 1); + * FNLfloat s = (x + y) * F2; + * x += s; y += s; + */ + + int i = FastFloor(x); + int j = FastFloor(y); + float xi = x - i; + float yi = y - j; + + float t = (xi + yi) * G2; + float x0 = xi - t; + float y0 = yi - t; + + i *= PrimeX; + j *= PrimeY; + + float vx, vy; + vx = vy = 0; + + float a = 0.5f - x0 * x0 - y0 * y0; + if (a > 0) { + float aaaa = (a * a) * (a * a); + float xo, yo; + if (outGradOnly) { + int hash = Hash(seed, i, j) & (255 << 1); + xo = RandVecs2D[hash]; + yo = RandVecs2D[hash | 1]; + } else { + int hash = Hash(seed, i, j); + int index1 = hash & (127 << 1); + int index2 = (hash >> 7) & (255 << 1); + float xg = Gradients2D[index1]; + float yg = Gradients2D[index1 | 1]; + float value = x0 * xg + y0 * yg; + float xgo = RandVecs2D[index2]; + float ygo = RandVecs2D[index2 | 1]; + xo = value * xgo; + yo = value * ygo; + } + vx += aaaa * xo; + vy += aaaa * yo; + } + + float c = 2 * (1 - 2 * G2) * (1 / G2 - 2) * t + ((-2 * (1 - 2 * G2) * (1 - 2 * G2)) + a); + if (c > 0) { + float x2 = x0 + (2 * G2 - 1); + float y2 = y0 + (2 * G2 - 1); + float cccc = (c * c) * (c * c); + float xo, yo; + if (outGradOnly) { + int hash = Hash(seed, i + PrimeX, j + PrimeY) & (255 << 1); + xo = RandVecs2D[hash]; + yo = RandVecs2D[hash | 1]; + } else { + int hash = Hash(seed, i + PrimeX, j + PrimeY); + int index1 = hash & (127 << 1); + int index2 = (hash >> 7) & (255 << 1); + float xg = Gradients2D[index1]; + float yg = Gradients2D[index1 | 1]; + float value = x2 * xg + y2 * yg; + float xgo = RandVecs2D[index2]; + float ygo = RandVecs2D[index2 | 1]; + xo = value * xgo; + yo = value * ygo; + } + vx += cccc * xo; + vy += cccc * yo; + } + + if (y0 > x0) { + float x1 = x0 + G2; + float y1 = y0 + (G2 - 1); + float b = 0.5f - x1 * x1 - y1 * y1; + if (b > 0) { + float bbbb = (b * b) * (b * b); + float xo, yo; + if (outGradOnly) { + int hash = Hash(seed, i, j + PrimeY) & (255 << 1); + xo = RandVecs2D[hash]; + yo = RandVecs2D[hash | 1]; + } else { + int hash = Hash(seed, i, j + PrimeY); + int index1 = hash & (127 << 1); + int index2 = (hash >> 7) & (255 << 1); + float xg = Gradients2D[index1]; + float yg = Gradients2D[index1 | 1]; + float value = x1 * xg + y1 * yg; + float xgo = RandVecs2D[index2]; + float ygo = RandVecs2D[index2 | 1]; + xo = value * xgo; + yo = value * ygo; + } + vx += bbbb * xo; + vy += bbbb * yo; + } + } else { + float x1 = x0 + (G2 - 1); + float y1 = y0 + G2; + float b = 0.5f - x1 * x1 - y1 * y1; + if (b > 0) { + float bbbb = (b * b) * (b * b); + float xo, yo; + if (outGradOnly) { + int hash = Hash(seed, i + PrimeX, j) & (255 << 1); + xo = RandVecs2D[hash]; + yo = RandVecs2D[hash | 1]; + } else { + int hash = Hash(seed, i + PrimeX, j); + int index1 = hash & (127 << 1); + int index2 = (hash >> 7) & (255 << 1); + float xg = Gradients2D[index1]; + float yg = Gradients2D[index1 | 1]; + float value = x1 * xg + y1 * yg; + float xgo = RandVecs2D[index2]; + float ygo = RandVecs2D[index2 | 1]; + xo = value * xgo; + yo = value * ygo; + } + vx += bbbb * xo; + vy += bbbb * yo; + } + } + + coord.x += vx * warpAmp; + coord.y += vy * warpAmp; + } + + + // Domain Warp Fractal Progressive + + private void singleDomainWarpOpenSimplex2Gradient(int seed, float warpAmp, float frequency, /*FNLfloat*/ float x, /*FNLfloat*/ float y, /*FNLfloat*/ float z, Vector3 coord, boolean outGradOnly) { + x *= frequency; + y *= frequency; + z *= frequency; + + /* + * --- Rotation moved to switch statements before fractal evaluation --- + * final FNLfloat R3 = (FNLfloat)(2.0 / 3.0); + * FNLfloat r = (x + y + z) * R3; // Rotation, not skew + * x = r - x; y = r - y; z = r - z; + */ + + int i = FastRound(x); + int j = FastRound(y); + int k = FastRound(z); + float x0 = x - i; + float y0 = y - j; + float z0 = z - k; + + int xNSign = (int) (-x0 - 1.0f) | 1; + int yNSign = (int) (-y0 - 1.0f) | 1; + int zNSign = (int) (-z0 - 1.0f) | 1; + + float ax0 = xNSign * -x0; + float ay0 = yNSign * -y0; + float az0 = zNSign * -z0; + + i *= PrimeX; + j *= PrimeY; + k *= PrimeZ; + + float vx, vy, vz; + vx = vy = vz = 0; + + float a = (0.6f - x0 * x0) - (y0 * y0 + z0 * z0); + for (int l = 0; ; l++) { + if (a > 0) { + float aaaa = (a * a) * (a * a); + float xo, yo, zo; + if (outGradOnly) { + int hash = Hash(seed, i, j, k) & (255 << 2); + xo = RandVecs3D[hash]; + yo = RandVecs3D[hash | 1]; + zo = RandVecs3D[hash | 2]; + } else { + int hash = Hash(seed, i, j, k); + int index1 = hash & (63 << 2); + int index2 = (hash >> 6) & (255 << 2); + float xg = Gradients3D[index1]; + float yg = Gradients3D[index1 | 1]; + float zg = Gradients3D[index1 | 2]; + float value = x0 * xg + y0 * yg + z0 * zg; + float xgo = RandVecs3D[index2]; + float ygo = RandVecs3D[index2 | 1]; + float zgo = RandVecs3D[index2 | 2]; + xo = value * xgo; + yo = value * ygo; + zo = value * zgo; + } + vx += aaaa * xo; + vy += aaaa * yo; + vz += aaaa * zo; + } + + float b = a; + int i1 = i; + int j1 = j; + int k1 = k; + float x1 = x0; + float y1 = y0; + float z1 = z0; + + if (ax0 >= ay0 && ax0 >= az0) { + x1 += xNSign; + b = b + ax0 + ax0; + i1 -= xNSign * PrimeX; + } else if (ay0 > ax0 && ay0 >= az0) { + y1 += yNSign; + b = b + ay0 + ay0; + j1 -= yNSign * PrimeY; + } else { + z1 += zNSign; + b = b + az0 + az0; + k1 -= zNSign * PrimeZ; + } + + if (b > 1) { + b -= 1; + float bbbb = (b * b) * (b * b); + float xo, yo, zo; + if (outGradOnly) { + int hash = Hash(seed, i1, j1, k1) & (255 << 2); + xo = RandVecs3D[hash]; + yo = RandVecs3D[hash | 1]; + zo = RandVecs3D[hash | 2]; + } else { + int hash = Hash(seed, i1, j1, k1); + int index1 = hash & (63 << 2); + int index2 = (hash >> 6) & (255 << 2); + float xg = Gradients3D[index1]; + float yg = Gradients3D[index1 | 1]; + float zg = Gradients3D[index1 | 2]; + float value = x1 * xg + y1 * yg + z1 * zg; + float xgo = RandVecs3D[index2]; + float ygo = RandVecs3D[index2 | 1]; + float zgo = RandVecs3D[index2 | 2]; + xo = value * xgo; + yo = value * ygo; + zo = value * zgo; + } + vx += bbbb * xo; + vy += bbbb * yo; + vz += bbbb * zo; + } + + if (l == 1) break; + + ax0 = 0.5f - ax0; + ay0 = 0.5f - ay0; + az0 = 0.5f - az0; + + x0 = xNSign * ax0; + y0 = yNSign * ay0; + z0 = zNSign * az0; + + a += (0.75f - ax0) - (ay0 + az0); + + i += (xNSign >> 1) & PrimeX; + j += (yNSign >> 1) & PrimeY; + k += (zNSign >> 1) & PrimeZ; + + xNSign = -xNSign; + yNSign = -yNSign; + zNSign = -zNSign; + + seed += 1293373; + } + + coord.x += vx * warpAmp; + coord.y += vy * warpAmp; + coord.z += vz * warpAmp; + } + + private enum TransformType3D { + None, + ImproveXYPlanes, + ImproveXZPlanes, + DefaultOpenSimplex2 + } + + public static class Vector2 { + public /*FNLfloat*/ float x; + public /*FNLfloat*/ float y; + + public Vector2(/*FNLfloat*/ float x, /*FNLfloat*/ float y) { + this.x = x; + this.y = y; + } + } + + public static class Vector3 { + public /*FNLfloat*/ float x; + public /*FNLfloat*/ float y; + public /*FNLfloat*/ float z; + + public Vector3(/*FNLfloat*/ float x, /*FNLfloat*/ float y, /*FNLfloat*/ float z) { + this.x = x; + this.y = y; + this.z = z; + } + } + +} \ No newline at end of file diff --git a/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/di/NoiseGenerationModule.kt b/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/di/NoiseGenerationModule.kt new file mode 100644 index 000000000..219bef741 --- /dev/null +++ b/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/di/NoiseGenerationModule.kt @@ -0,0 +1,37 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package ru.tech.imageresizershrinker.noise_generation.di + +import android.graphics.Bitmap +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import ru.tech.imageresizershrinker.noise_generation.data.AndroidNoiseGenerator +import ru.tech.imageresizershrinker.noise_generation.domain.NoiseGenerator + +@Module +@InstallIn(SingletonComponent::class) +internal interface NoiseGenerationModule { + + @Binds + fun provideGenerator( + impl: AndroidNoiseGenerator + ): NoiseGenerator + +} \ No newline at end of file diff --git a/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/domain/NoiseGenerator.kt b/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/domain/NoiseGenerator.kt new file mode 100644 index 000000000..29afba21c --- /dev/null +++ b/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/domain/NoiseGenerator.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package ru.tech.imageresizershrinker.noise_generation.domain + +import ru.tech.imageresizershrinker.noise_generation.domain.model.NoiseParams + +interface NoiseGenerator { + + suspend fun generateNoise( + width: Int, + height: Int, + noiseParams: NoiseParams, + onFailure: (Throwable) -> Unit = {} + ): Image? + +} \ No newline at end of file diff --git a/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/domain/model/CellularDistanceFunction.kt b/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/domain/model/CellularDistanceFunction.kt new file mode 100644 index 000000000..1a501828a --- /dev/null +++ b/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/domain/model/CellularDistanceFunction.kt @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package ru.tech.imageresizershrinker.noise_generation.domain.model + +enum class CellularDistanceFunction { + Euclidean, + EuclideanSq, + Manhattan, + Hybrid +} \ No newline at end of file diff --git a/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/domain/model/CellularReturnType.kt b/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/domain/model/CellularReturnType.kt new file mode 100644 index 000000000..f158a8abc --- /dev/null +++ b/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/domain/model/CellularReturnType.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package ru.tech.imageresizershrinker.noise_generation.domain.model + +enum class CellularReturnType { + CellValue, + Distance, + Distance2, + Distance2Add, + Distance2Sub, + Distance2Mul, + Distance2Div +} \ No newline at end of file diff --git a/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/domain/model/DomainWarpType.kt b/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/domain/model/DomainWarpType.kt new file mode 100644 index 000000000..f810e6a77 --- /dev/null +++ b/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/domain/model/DomainWarpType.kt @@ -0,0 +1,24 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package ru.tech.imageresizershrinker.noise_generation.domain.model + +enum class DomainWarpType { + OpenSimplex2, + OpenSimplex2Reduced, + BasicGrid +} \ No newline at end of file diff --git a/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/domain/model/FractalType.kt b/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/domain/model/FractalType.kt new file mode 100644 index 000000000..b004e7728 --- /dev/null +++ b/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/domain/model/FractalType.kt @@ -0,0 +1,27 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package ru.tech.imageresizershrinker.noise_generation.domain.model + +enum class FractalType { + None, + FBm, + Ridged, + PingPong, + DomainWarpProgressive, + DomainWarpIndependent +} \ No newline at end of file diff --git a/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/domain/model/NoiseParams.kt b/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/domain/model/NoiseParams.kt new file mode 100644 index 000000000..4abe28605 --- /dev/null +++ b/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/domain/model/NoiseParams.kt @@ -0,0 +1,58 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package ru.tech.imageresizershrinker.noise_generation.domain.model + +data class NoiseParams( + val seed: Int, + val frequency: Float, + val noiseType: NoiseType, + val rotationType3D: RotationType3D, + val fractalType: FractalType, + val fractalOctaves: Int, + val fractalLacunarity: Float, + val fractalGain: Float, + val fractalWeightedStrength: Float, + val fractalPingPongStrength: Float, + val cellularDistanceFunction: CellularDistanceFunction, + val cellularReturnType: CellularReturnType, + val cellularJitter: Float, + val domainWarpType: DomainWarpType, + val domainWarpAmp: Float +) { + companion object { + val Default by lazy { + NoiseParams( + seed = 1337, + frequency = 0.01f, + noiseType = NoiseType.OpenSimplex2, + rotationType3D = RotationType3D.None, + fractalType = FractalType.None, + fractalOctaves = 3, + fractalLacunarity = 2f, + fractalGain = 0.5f, + fractalWeightedStrength = 0f, + fractalPingPongStrength = 2f, + cellularDistanceFunction = CellularDistanceFunction.EuclideanSq, + cellularReturnType = CellularReturnType.Distance, + cellularJitter = 1f, + domainWarpType = DomainWarpType.OpenSimplex2, + domainWarpAmp = 1f + ) + } + } +} \ No newline at end of file diff --git a/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/domain/model/NoiseType.kt b/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/domain/model/NoiseType.kt new file mode 100644 index 000000000..fee0f9a6c --- /dev/null +++ b/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/domain/model/NoiseType.kt @@ -0,0 +1,27 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package ru.tech.imageresizershrinker.noise_generation.domain.model + +enum class NoiseType { + OpenSimplex2, + OpenSimplex2S, + Cellular, + Perlin, + ValueCubic, + Value +} \ No newline at end of file diff --git a/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/domain/model/RotationType3D.kt b/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/domain/model/RotationType3D.kt new file mode 100644 index 000000000..99a0edf3f --- /dev/null +++ b/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/domain/model/RotationType3D.kt @@ -0,0 +1,24 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package ru.tech.imageresizershrinker.noise_generation.domain.model + +enum class RotationType3D { + None, + ImproveXYPlanes, + ImproveXZPlanes +} \ No newline at end of file diff --git a/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/presentation/NoiseGenerationContent.kt b/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/presentation/NoiseGenerationContent.kt new file mode 100644 index 000000000..d1cb3aec0 --- /dev/null +++ b/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/presentation/NoiseGenerationContent.kt @@ -0,0 +1,224 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package ru.tech.imageresizershrinker.noise_generation.presentation + +import android.net.Uri +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.height +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import dev.olshevski.navigation.reimagined.hilt.hiltViewModel +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import ru.tech.imageresizershrinker.core.domain.image.model.ImageInfo +import ru.tech.imageresizershrinker.core.resources.R +import ru.tech.imageresizershrinker.core.ui.utils.confetti.LocalConfettiHostState +import ru.tech.imageresizershrinker.core.ui.utils.helper.asClip +import ru.tech.imageresizershrinker.core.ui.utils.helper.isPortraitOrientationAsState +import ru.tech.imageresizershrinker.core.ui.utils.helper.parseSaveResult +import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen +import ru.tech.imageresizershrinker.core.ui.utils.state.derivedValueOf +import ru.tech.imageresizershrinker.core.ui.widget.AdaptiveLayoutScreen +import ru.tech.imageresizershrinker.core.ui.widget.buttons.BottomButtonsBlock +import ru.tech.imageresizershrinker.core.ui.widget.buttons.ShareButton +import ru.tech.imageresizershrinker.core.ui.widget.controls.ResizeImageField +import ru.tech.imageresizershrinker.core.ui.widget.controls.selection.ImageFormatSelector +import ru.tech.imageresizershrinker.core.ui.widget.controls.selection.QualitySelector +import ru.tech.imageresizershrinker.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog +import ru.tech.imageresizershrinker.core.ui.widget.image.Picture +import ru.tech.imageresizershrinker.core.ui.widget.modifier.container +import ru.tech.imageresizershrinker.core.ui.widget.other.Loading +import ru.tech.imageresizershrinker.core.ui.widget.other.LoadingDialog +import ru.tech.imageresizershrinker.core.ui.widget.other.LocalToastHostState +import ru.tech.imageresizershrinker.core.ui.widget.other.TopAppBarEmoji +import ru.tech.imageresizershrinker.core.ui.widget.sheets.ProcessImagesPreferenceSheet +import ru.tech.imageresizershrinker.core.ui.widget.text.marquee +import ru.tech.imageresizershrinker.noise_generation.presentation.components.NoiseParamsSelection +import ru.tech.imageresizershrinker.noise_generation.presentation.viewModel.NoiseGenerationViewModel + +@Composable +fun NoiseGenerationContent( + onGoBack: () -> Unit, + onNavigate: (Screen) -> Unit, + viewModel: NoiseGenerationViewModel = hiltViewModel() +) { + val toastHostState = LocalToastHostState.current + val context = LocalContext.current + val scope = rememberCoroutineScope() + val confettiHostState = LocalConfettiHostState.current + + val showConfetti: () -> Unit = { + scope.launch { + confettiHostState.showConfetti() + } + } + + val isPortrait by isPortraitOrientationAsState() + + val saveBitmap: (oneTimeSaveLocationUri: String?) -> Unit = { + viewModel.saveNoise(it) { saveResult -> + context.parseSaveResult( + saveResult = saveResult, + onSuccess = showConfetti, + toastHostState = toastHostState, + scope = scope + ) + } + } + + AdaptiveLayoutScreen( + title = { + Text( + text = stringResource(R.string.noise_generation), + textAlign = TextAlign.Center, + modifier = Modifier.marquee() + ) + }, + onGoBack = onGoBack, + actions = { + var editSheetData by remember { + mutableStateOf(listOf()) + } + ShareButton( + onShare = { + viewModel.shareNoise(showConfetti) + }, + onCopy = { manager -> + viewModel.cacheCurrentNoise { uri -> + manager.setClip(uri.asClip(context)) + showConfetti() + } + }, + onEdit = { + viewModel.cacheCurrentNoise { + editSheetData = listOf(it) + } + } + ) + ProcessImagesPreferenceSheet( + uris = editSheetData, + visible = editSheetData.isNotEmpty(), + onDismiss = { + if (!it) { + editSheetData = emptyList() + } + }, + onNavigate = { screen -> + scope.launch { + editSheetData = emptyList() + delay(200) + onNavigate(screen) + } + } + ) + }, + topAppBarPersistentActions = { + TopAppBarEmoji() + }, + imagePreview = { + Box( + contentAlignment = Alignment.Center + ) { + Picture( + model = viewModel.previewBitmap, + modifier = Modifier + .container(MaterialTheme.shapes.medium) + .aspectRatio(viewModel.noiseSize.safeAspectRatio), + shape = MaterialTheme.shapes.medium, + contentScale = ContentScale.FillBounds + ) + if (viewModel.isImageLoading) Loading() + } + }, + controls = { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + ResizeImageField( + imageInfo = derivedValueOf(viewModel.noiseSize) { + ImageInfo(viewModel.noiseSize.width, viewModel.noiseSize.height) + }, + originalSize = null, + onWidthChange = viewModel::setNoiseWidth, + onHeightChange = viewModel::setNoiseHeight + ) + NoiseParamsSelection( + value = viewModel.noiseParams, + onValueChange = viewModel::updateParams + ) + Spacer(Modifier.height(4.dp)) + ImageFormatSelector( + value = viewModel.imageFormat, + onValueChange = viewModel::setImageFormat + ) + QualitySelector( + quality = viewModel.quality, + imageFormat = viewModel.imageFormat, + onQualityChange = viewModel::setQuality + ) + } + }, + buttons = { + var showFolderSelectionDialog by rememberSaveable { + mutableStateOf(false) + } + BottomButtonsBlock( + targetState = false to isPortrait, + isSecondaryButtonVisible = false, + onSecondaryButtonClick = {}, + onPrimaryButtonClick = { + saveBitmap(null) + }, + onPrimaryButtonLongClick = { + showFolderSelectionDialog = true + }, + actions = it + ) + if (showFolderSelectionDialog) { + OneTimeSaveLocationSelectionDialog( + onDismiss = { showFolderSelectionDialog = false }, + onSaveRequest = saveBitmap + ) + } + }, + canShowScreenData = true, + isPortrait = isPortrait + ) + + if (viewModel.isSaving) { + LoadingDialog(onCancelLoading = viewModel::cancelSaving) + } +} \ No newline at end of file diff --git a/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/presentation/components/NoiseParamsSelection.kt b/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/presentation/components/NoiseParamsSelection.kt new file mode 100644 index 000000000..168f521aa --- /dev/null +++ b/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/presentation/components/NoiseParamsSelection.kt @@ -0,0 +1,253 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package ru.tech.imageresizershrinker.noise_generation.presentation.components + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.RampLeft +import androidx.compose.material.icons.outlined.SettingsEthernet +import androidx.compose.material.icons.outlined.Waves +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.smarttoolfactory.extendedcolors.util.roundToTwoDigits +import ru.tech.imageresizershrinker.core.domain.utils.roundTo +import ru.tech.imageresizershrinker.core.resources.R +import ru.tech.imageresizershrinker.core.ui.widget.controls.EnhancedSliderItem +import ru.tech.imageresizershrinker.core.ui.widget.controls.selection.DataSelector +import ru.tech.imageresizershrinker.noise_generation.domain.model.CellularDistanceFunction +import ru.tech.imageresizershrinker.noise_generation.domain.model.CellularReturnType +import ru.tech.imageresizershrinker.noise_generation.domain.model.DomainWarpType +import ru.tech.imageresizershrinker.noise_generation.domain.model.FractalType +import ru.tech.imageresizershrinker.noise_generation.domain.model.NoiseParams +import ru.tech.imageresizershrinker.noise_generation.domain.model.NoiseType +import kotlin.math.roundToInt + +@Composable +fun NoiseParamsSelection( + value: NoiseParams, + onValueChange: (NoiseParams) -> Unit +) { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + EnhancedSliderItem( + value = value.seed, + icon = Icons.Outlined.SettingsEthernet, + title = stringResource(R.string.seed), + valueRange = -10000f..10000f, + internalStateTransformation = { + it.roundToInt() + }, + onValueChange = { + onValueChange(value.copy(seed = it.toInt())) + }, + shape = RoundedCornerShape(24.dp) + ) + EnhancedSliderItem( + value = value.frequency, + icon = Icons.Outlined.Waves, + title = stringResource(R.string.frequency), + valueRange = -0.5f..0.5f, + internalStateTransformation = { + it.roundTo(3) + }, + onValueChange = { + onValueChange(value.copy(frequency = it)) + }, + shape = RoundedCornerShape(24.dp) + ) + DataSelector( + value = value.noiseType, + onValueChange = { + onValueChange(value.copy(noiseType = it)) + }, + entries = NoiseType.entries, + title = stringResource(R.string.noise_type), + titleIcon = null, + itemContentText = { + it.name + }, + spanCount = 2, + color = Color.Unspecified + ) + DataSelector( + value = value.fractalType, + onValueChange = { + onValueChange(value.copy(fractalType = it)) + }, + entries = FractalType.entries, + title = stringResource(R.string.fractal_type), + titleIcon = null, + itemContentText = { + it.name + }, + spanCount = 2, + color = Color.Unspecified + ) + AnimatedVisibility(value.fractalType != FractalType.None) { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + EnhancedSliderItem( + value = value.fractalOctaves, + title = stringResource(R.string.octaves), + valueRange = 1f..5f, + steps = 3, + internalStateTransformation = { + it.roundToInt() + }, + onValueChange = { + onValueChange(value.copy(fractalOctaves = it.toInt())) + }, + shape = RoundedCornerShape(24.dp) + ) + EnhancedSliderItem( + value = value.fractalLacunarity, + title = stringResource(R.string.lacunarity), + valueRange = -50f..50f, + internalStateTransformation = { + it.roundToTwoDigits() + }, + onValueChange = { + onValueChange(value.copy(fractalLacunarity = it)) + }, + shape = RoundedCornerShape(24.dp) + ) + EnhancedSliderItem( + value = value.fractalGain, + title = stringResource(R.string.gain), + valueRange = -10f..10f, + internalStateTransformation = { + it.roundToTwoDigits() + }, + onValueChange = { + onValueChange(value.copy(fractalGain = it)) + }, + shape = RoundedCornerShape(24.dp) + ) + EnhancedSliderItem( + value = value.fractalWeightedStrength, + title = stringResource(R.string.weighted_strength), + valueRange = -3f..3f, + internalStateTransformation = { + it.roundToTwoDigits() + }, + onValueChange = { + onValueChange(value.copy(fractalWeightedStrength = it)) + }, + shape = RoundedCornerShape(24.dp) + ) + AnimatedVisibility(value.fractalType == FractalType.PingPong) { + EnhancedSliderItem( + value = value.fractalPingPongStrength, + title = stringResource(R.string.ping_pong_strength), + valueRange = 0f..20f, + internalStateTransformation = { + it.roundToTwoDigits() + }, + onValueChange = { + onValueChange(value.copy(fractalPingPongStrength = it)) + }, + shape = RoundedCornerShape(24.dp) + ) + } + AnimatedVisibility(value.noiseType == NoiseType.Cellular) { + Column { + DataSelector( + value = value.cellularDistanceFunction, + onValueChange = { + onValueChange(value.copy(cellularDistanceFunction = it)) + }, + entries = CellularDistanceFunction.entries, + title = stringResource(R.string.distance_function), + titleIcon = null, + itemContentText = { + it.name + }, + spanCount = 2, + color = Color.Unspecified + ) + Spacer(Modifier.height(8.dp)) + DataSelector( + value = value.cellularReturnType, + onValueChange = { + onValueChange(value.copy(cellularReturnType = it)) + }, + entries = CellularReturnType.entries, + title = stringResource(R.string.return_type), + titleIcon = null, + itemContentText = { + it.name + }, + spanCount = 2, + color = Color.Unspecified + ) + Spacer(Modifier.height(8.dp)) + EnhancedSliderItem( + value = value.cellularJitter, + title = stringResource(R.string.jitter), + valueRange = -10f..10f, + internalStateTransformation = { + it.roundToTwoDigits() + }, + onValueChange = { + onValueChange(value.copy(cellularJitter = it)) + }, + shape = RoundedCornerShape(24.dp) + ) + } + } + } + } + DataSelector( + value = value.domainWarpType, + onValueChange = { + onValueChange(value.copy(domainWarpType = it)) + }, + entries = DomainWarpType.entries, + title = stringResource(R.string.domain_warp), + titleIcon = null, + itemContentText = { + it.name + }, + spanCount = 2, + color = Color.Unspecified + ) + EnhancedSliderItem( + value = value.domainWarpAmp, + icon = Icons.Outlined.RampLeft, + title = stringResource(R.string.amplitude), + valueRange = -2000f..2000f, + internalStateTransformation = { + it.roundToTwoDigits() + }, + onValueChange = { + onValueChange(value.copy(domainWarpAmp = it)) + }, + shape = RoundedCornerShape(24.dp) + ) + } +} \ No newline at end of file diff --git a/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/presentation/viewModel/NoiseGenerationViewModel.kt b/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/presentation/viewModel/NoiseGenerationViewModel.kt new file mode 100644 index 000000000..806f03d86 --- /dev/null +++ b/feature/noise-generation/src/main/java/ru/tech/imageresizershrinker/noise_generation/presentation/viewModel/NoiseGenerationViewModel.kt @@ -0,0 +1,205 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package ru.tech.imageresizershrinker.noise_generation.presentation.viewModel + +import android.graphics.Bitmap +import android.net.Uri +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.core.net.toUri +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Job +import ru.tech.imageresizershrinker.core.domain.dispatchers.DispatchersHolder +import ru.tech.imageresizershrinker.core.domain.image.ImageCompressor +import ru.tech.imageresizershrinker.core.domain.image.ImageScaler +import ru.tech.imageresizershrinker.core.domain.image.ShareProvider +import ru.tech.imageresizershrinker.core.domain.image.model.ImageFormat +import ru.tech.imageresizershrinker.core.domain.image.model.ImageInfo +import ru.tech.imageresizershrinker.core.domain.image.model.Quality +import ru.tech.imageresizershrinker.core.domain.model.IntegerSize +import ru.tech.imageresizershrinker.core.domain.saving.FileController +import ru.tech.imageresizershrinker.core.domain.saving.model.ImageSaveTarget +import ru.tech.imageresizershrinker.core.domain.saving.model.SaveResult +import ru.tech.imageresizershrinker.core.domain.utils.smartJob +import ru.tech.imageresizershrinker.core.ui.utils.BaseViewModel +import ru.tech.imageresizershrinker.core.ui.utils.state.update +import ru.tech.imageresizershrinker.noise_generation.domain.NoiseGenerator +import ru.tech.imageresizershrinker.noise_generation.domain.model.NoiseParams +import javax.inject.Inject + +@HiltViewModel +class NoiseGenerationViewModel @Inject constructor( + dispatchersHolder: DispatchersHolder, + private val noiseGenerator: NoiseGenerator, + private val fileController: FileController, + private val shareProvider: ShareProvider, + private val imageCompressor: ImageCompressor, + private val imageScaler: ImageScaler +) : BaseViewModel(dispatchersHolder) { + + private val _previewBitmap: MutableState = mutableStateOf(null) + val previewBitmap: Bitmap? by _previewBitmap + + private val _noiseParams: MutableState = mutableStateOf(NoiseParams.Default) + val noiseParams: NoiseParams by _noiseParams + + private val _noiseSize: MutableState = mutableStateOf(IntegerSize(1000, 1000)) + val noiseSize: IntegerSize by _noiseSize + + private val _imageFormat: MutableState = mutableStateOf(ImageFormat.Default) + val imageFormat: ImageFormat by _imageFormat + + private val _quality: MutableState = mutableStateOf(Quality.Base(100)) + val quality: Quality by _quality + + private val _isSaving: MutableState = mutableStateOf(false) + val isSaving by _isSaving + + private var savingJob: Job? by smartJob { + _isSaving.update { false } + } + + fun saveNoise( + oneTimeSaveLocationUri: String?, + onComplete: (result: SaveResult) -> Unit, + ) { + savingJob = viewModelScope.launch(defaultDispatcher) { + _isSaving.update { true } + noiseGenerator.generateNoise( + width = noiseSize.width, + height = noiseSize.height, + noiseParams = noiseParams, + onFailure = { + onComplete(SaveResult.Error.Exception(it)) + } + )?.let { bitmap -> + val imageInfo = ImageInfo( + width = bitmap.width, + height = bitmap.height, + quality = quality, + imageFormat = imageFormat + ) + onComplete( + fileController.save( + saveTarget = ImageSaveTarget( + imageInfo = imageInfo, + metadata = null, + originalUri = "Noise", + sequenceNumber = null, + data = imageCompressor.compress( + image = bitmap, + imageFormat = imageFormat, + quality = quality + ) + ), + keepOriginalMetadata = true, + oneTimeSaveLocationUri = oneTimeSaveLocationUri + ).onSuccess(::registerSave) + ) + } + _isSaving.update { false } + } + } + + fun cacheCurrentNoise(onComplete: (Uri) -> Unit) { + savingJob = viewModelScope.launch { + _isSaving.update { true } + noiseGenerator.generateNoise( + width = noiseSize.width, + height = noiseSize.height, + noiseParams = noiseParams + )?.let { image -> + val imageInfo = ImageInfo( + width = image.width, + height = image.height, + quality = quality, + imageFormat = imageFormat + ) + shareProvider.cacheImage( + image = image, + imageInfo = imageInfo + )?.let { uri -> + onComplete(uri.toUri()) + } + } + _isSaving.update { false } + } + } + + fun shareNoise(onComplete: () -> Unit) { + cacheCurrentNoise { uri -> + viewModelScope.launch { + shareProvider.shareUri( + uri = uri.toString(), + onComplete = onComplete + ) + } + } + } + + fun cancelSaving() { + savingJob?.cancel() + savingJob = null + _isSaving.update { false } + } + + fun setImageFormat(imageFormat: ImageFormat) { + _imageFormat.update { imageFormat } + } + + fun setQuality(quality: Quality) { + _quality.update { quality } + } + + fun updateParams(params: NoiseParams) { + _noiseParams.update { params } + updatePreview() + } + + fun setNoiseWidth(width: Int) { + _noiseSize.update { it.copy(width = width.coerceAtMost(2048)) } + updatePreview() + } + + fun setNoiseHeight(height: Int) { + _noiseSize.update { it.copy(height = height.coerceAtMost(2048)) } + updatePreview() + } + + private fun updatePreview() { + viewModelScope.launch { + _isImageLoading.update { true } + _previewBitmap.update { null } + noiseGenerator.generateNoise( + width = noiseSize.width, + height = noiseSize.height, + noiseParams = noiseParams + ).also { bitmap -> + _previewBitmap.update { bitmap } + _isImageLoading.update { false } + } + } + } + + init { + updatePreview() + } + +} \ No newline at end of file diff --git a/feature/root/build.gradle.kts b/feature/root/build.gradle.kts index a26654cd8..cd2c2455b 100644 --- a/feature/root/build.gradle.kts +++ b/feature/root/build.gradle.kts @@ -60,4 +60,5 @@ dependencies { implementation(projects.feature.imageSplitting) implementation(projects.feature.colorTools) implementation(projects.feature.webpTools) + implementation(projects.feature.noiseGeneration) } \ No newline at end of file diff --git a/feature/root/src/main/java/ru/tech/imageresizershrinker/feature/root/presentation/components/ScreenSelector.kt b/feature/root/src/main/java/ru/tech/imageresizershrinker/feature/root/presentation/components/ScreenSelector.kt index f491ce20b..88b286e47 100644 --- a/feature/root/src/main/java/ru/tech/imageresizershrinker/feature/root/presentation/components/ScreenSelector.kt +++ b/feature/root/src/main/java/ru/tech/imageresizershrinker/feature/root/presentation/components/ScreenSelector.kt @@ -73,6 +73,7 @@ import ru.tech.imageresizershrinker.feature.watermarking.presentation.Watermarki import ru.tech.imageresizershrinker.feature.webp_tools.presentation.WebpToolsContent import ru.tech.imageresizershrinker.feature.zip.presentation.ZipContent import ru.tech.imageresizershrinker.image_splitting.presentation.ImageSplitterContent +import ru.tech.imageresizershrinker.noise_generation.presentation.NoiseGenerationContent @Composable internal fun ScreenSelector( @@ -400,6 +401,13 @@ internal fun ScreenSelector( onNavigate = onNavigate ) } + + Screen.NoiseGeneration -> { + NoiseGenerationContent( + onGoBack = onGoBack, + onNavigate = onNavigate + ) + } } } ScreenBasedMaxBrightnessEnforcement(navController.currentDestination()) diff --git a/feature/svg-maker/src/main/java/ru/tech/imageresizershrinker/feature/svg_maker/data/SvgManagerImpl.kt b/feature/svg-maker/src/main/java/ru/tech/imageresizershrinker/feature/svg_maker/data/AndroidSvgManager.kt similarity index 98% rename from feature/svg-maker/src/main/java/ru/tech/imageresizershrinker/feature/svg_maker/data/SvgManagerImpl.kt rename to feature/svg-maker/src/main/java/ru/tech/imageresizershrinker/feature/svg_maker/data/AndroidSvgManager.kt index e022bb530..e090d8f13 100644 --- a/feature/svg-maker/src/main/java/ru/tech/imageresizershrinker/feature/svg_maker/data/SvgManagerImpl.kt +++ b/feature/svg-maker/src/main/java/ru/tech/imageresizershrinker/feature/svg_maker/data/AndroidSvgManager.kt @@ -38,7 +38,7 @@ import java.io.FileWriter import javax.inject.Inject -internal class SvgManagerImpl @Inject constructor( +internal class AndroidSvgManager @Inject constructor( @ApplicationContext private val context: Context, private val randomStringGenerator: RandomStringGenerator, private val imageGetter: ImageGetter, diff --git a/feature/svg-maker/src/main/java/ru/tech/imageresizershrinker/feature/svg_maker/di/SvgMakerModule.kt b/feature/svg-maker/src/main/java/ru/tech/imageresizershrinker/feature/svg_maker/di/SvgMakerModule.kt index d003eae56..fa3ed1fab 100644 --- a/feature/svg-maker/src/main/java/ru/tech/imageresizershrinker/feature/svg_maker/di/SvgMakerModule.kt +++ b/feature/svg-maker/src/main/java/ru/tech/imageresizershrinker/feature/svg_maker/di/SvgMakerModule.kt @@ -21,7 +21,7 @@ import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent -import ru.tech.imageresizershrinker.feature.svg_maker.data.SvgManagerImpl +import ru.tech.imageresizershrinker.feature.svg_maker.data.AndroidSvgManager import ru.tech.imageresizershrinker.feature.svg_maker.domain.SvgManager import javax.inject.Singleton @@ -33,7 +33,7 @@ internal interface SvgMakerModule { @Singleton @Binds fun provideSvgManager( - manager: SvgManagerImpl + manager: AndroidSvgManager ): SvgManager } \ No newline at end of file diff --git a/feature/zip/src/main/java/ru/tech/imageresizershrinker/feature/zip/data/ZipManagerImpl.kt b/feature/zip/src/main/java/ru/tech/imageresizershrinker/feature/zip/data/AndroidZipManager.kt similarity index 97% rename from feature/zip/src/main/java/ru/tech/imageresizershrinker/feature/zip/data/ZipManagerImpl.kt rename to feature/zip/src/main/java/ru/tech/imageresizershrinker/feature/zip/data/AndroidZipManager.kt index 9a0d6ceac..05290562b 100644 --- a/feature/zip/src/main/java/ru/tech/imageresizershrinker/feature/zip/data/ZipManagerImpl.kt +++ b/feature/zip/src/main/java/ru/tech/imageresizershrinker/feature/zip/data/AndroidZipManager.kt @@ -32,7 +32,7 @@ import java.util.zip.ZipEntry import java.util.zip.ZipOutputStream import javax.inject.Inject -internal class ZipManagerImpl @Inject constructor( +internal class AndroidZipManager @Inject constructor( @ApplicationContext private val context: Context, dispatchersHolder: DispatchersHolder ) : DispatchersHolder by dispatchersHolder, ZipManager { diff --git a/feature/zip/src/main/java/ru/tech/imageresizershrinker/feature/zip/di/ZipModule.kt b/feature/zip/src/main/java/ru/tech/imageresizershrinker/feature/zip/di/ZipModule.kt index 8964da3f9..6c5a9eda0 100644 --- a/feature/zip/src/main/java/ru/tech/imageresizershrinker/feature/zip/di/ZipModule.kt +++ b/feature/zip/src/main/java/ru/tech/imageresizershrinker/feature/zip/di/ZipModule.kt @@ -21,7 +21,7 @@ import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent -import ru.tech.imageresizershrinker.feature.zip.data.ZipManagerImpl +import ru.tech.imageresizershrinker.feature.zip.data.AndroidZipManager import ru.tech.imageresizershrinker.feature.zip.domain.ZipManager import javax.inject.Singleton @@ -33,7 +33,7 @@ internal interface ZipModule { @Singleton @Binds fun provideZipManager( - manager: ZipManagerImpl + manager: AndroidZipManager ): ZipManager } \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 3fcb72a82..f904e5a76 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -17,12 +17,6 @@ @file:Suppress("UnstableApiUsage") -include(":feature:color-tools") - - -include(":feature:image-splitting") - - pluginManagement { repositories { includeBuild("build-logic") @@ -103,7 +97,10 @@ include(":feature:format-conversion") include(":feature:document-scanner") include(":feature:scan-qr-code") include(":feature:image-stacking") +include(":feature:image-splitting") +include(":feature:color-tools") include(":feature:webp-tools") +include(":feature:noise-generation") include(":feature:root")