Sell PRO as an in-app unlock, take shared photos, and let FX run negative
- One free Play app (com.locphamtran.recipescamera) with an expo-iap PRO unlock, replacing the paid apk for the store channel - Android SEND/SEND_MULTIPLE share target feeds photos into the editor, on cold start and on resume - BACK button in the TopBar and press-and-hold PEEK replace the chip row - NOISE REDUCTION and SHARPENING accept -10: a negative SHARPENING softens the frame before the grain pass, a negative NOISE REDUCTION re-grains it, in the Skia engine, the live preview and the Kotlin export path alike
This commit is contained in:
@@ -335,6 +335,24 @@ export async function processAndExportPhoto(
|
||||
}
|
||||
}
|
||||
|
||||
// Negative SHARPENING is the amount of digital edge enhancement to take
|
||||
// back OUT. It runs HERE, inside the photo's own filter chain, because the
|
||||
// post-grain twin (step 9) blurred the grain overlay away: NR < 0 plus
|
||||
// SHARPENING < 0 exported as flat mush with no grain at all.
|
||||
const sharpKnob = adjustments.sharpening ?? 0;
|
||||
if (sharpKnob < 0) {
|
||||
const softenSigma = (Math.abs(sharpKnob) / 10) * 3;
|
||||
const softenFilter = Skia.ImageFilter.MakeBlur(
|
||||
softenSigma,
|
||||
softenSigma,
|
||||
TileMode.Clamp,
|
||||
imageFilter
|
||||
);
|
||||
if (softenFilter) {
|
||||
imageFilter = softenFilter;
|
||||
}
|
||||
}
|
||||
|
||||
if (imageFilter) {
|
||||
paint.setImageFilter(imageFilter);
|
||||
}
|
||||
@@ -423,8 +441,18 @@ export async function processAndExportPhoto(
|
||||
|
||||
|
||||
// 6. Add Monochrome Grain Overlay
|
||||
if (adjustments.grain > 0) {
|
||||
const grainOpacity = adjustments.grain / 20; // up to 0.5 opacity
|
||||
// NOISE REDUCTION below 0 works the other way round: it hands back the grain
|
||||
// the denoiser would have flattened (a JPEG cannot be un-smoothed, but it
|
||||
// can be re-grained). Same overlay as GRAIN, capped at the GRAIN slider's
|
||||
// own ceiling so a negative NR plus GRAIN never double up.
|
||||
// ponytail: re-grain, not de-noise; add a real grain-preserving NR the day
|
||||
// the pipeline gets a pre-ISP raw frame.
|
||||
const grainAmount = Math.min(
|
||||
10,
|
||||
Math.max(0, adjustments.grain) + Math.max(0, -(adjustments.denoise ?? 0))
|
||||
);
|
||||
if (grainAmount > 0) {
|
||||
const grainOpacity = grainAmount / 20; // up to 0.5 opacity
|
||||
const grainPaint = Skia.Paint();
|
||||
grainPaint.setBlendMode(BlendMode.Overlay);
|
||||
grainPaint.setAlphaf(grainOpacity);
|
||||
@@ -732,8 +760,10 @@ export async function processAndExportPhoto(
|
||||
let resultImage = outputSurface.makeImageSnapshot();
|
||||
// Sharpening knob (0..10) overrides the legacy boolean sharpen flag:
|
||||
// legacy camera captures keep sharpen:true → 0.5 fallback when knob is 0.
|
||||
const sharpKnob = adjustments.sharpening ?? 0;
|
||||
const sharpenAmount = sharpKnob > 0 ? (sharpKnob / 10) * 0.8 : options?.sharpen ? 0.5 : 0;
|
||||
// A negative knob means "no sharpening at all" (its blur already ran at
|
||||
// step 4, under the grain), so the legacy 0.5 fallback must not fire.
|
||||
const sharpenAmount =
|
||||
sharpKnob > 0 ? (sharpKnob / 10) * 0.8 : sharpKnob < 0 ? 0 : options?.sharpen ? 0.5 : 0;
|
||||
if (sharpenAmount > 0) {
|
||||
resultImage = screenSharpenImage(resultImage, sharpenAmount);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user