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:
@@ -468,13 +468,22 @@ const Viewfinder = forwardRef<ViewfinderHandle, ViewfinderProps>(function Viewfi
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[]
|
||||
);
|
||||
// Sharpening (-10..10): below 0 the knob softens the frame, the same blur the
|
||||
// export applies. Own sync so it wraps the clarity filter instead of the two
|
||||
// knobs clobbering each other.
|
||||
const softenSync = useMemo(
|
||||
() => createSynchronizable<number>(adjustments.sharpening ?? 0),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[]
|
||||
);
|
||||
useEffect(() => {
|
||||
colorMatrixSync.setBlocking(colorMatrix);
|
||||
toneSync.setBlocking(toneParams);
|
||||
cinemaSync.setBlocking(cinemaParams ? cinemaParams.flat : cinemaOffFlat());
|
||||
hdfSync.setBlocking(adjustments.hdf ?? 0);
|
||||
claritySync.setBlocking(adjustments.clarity ?? 0);
|
||||
}, [colorMatrix, toneParams, cinemaParams, adjustments.hdf, adjustments.clarity, colorMatrixSync, toneSync, cinemaSync, hdfSync, claritySync]);
|
||||
softenSync.setBlocking(adjustments.sharpening ?? 0);
|
||||
}, [colorMatrix, toneParams, cinemaParams, adjustments.hdf, adjustments.clarity, adjustments.sharpening, colorMatrixSync, toneSync, cinemaSync, hdfSync, claritySync, softenSync]);
|
||||
|
||||
// Camera controller lives behind SkiaCamera. The camera AE bias is never
|
||||
// written: EV (user + metering offset) is purely software matrix gain, for
|
||||
@@ -1134,38 +1143,50 @@ const Viewfinder = forwardRef<ViewfinderHandle, ViewfinderProps>(function Viewfi
|
||||
console.warn('Color matrix build failed: ' + e);
|
||||
}
|
||||
// CLARITY: the export pass 4 twin — unsharp convolution above 0, mist
|
||||
// blur below. Applied to the base paint so it sits under the glow and
|
||||
// the panel actually shows what the file will get.
|
||||
// blur below. SHARPENING < 0 rides the same slot: it softens the frame
|
||||
// the exported file gets too. Both sit on the base paint, under the
|
||||
// glow, and compose (soften wraps whatever clarity built).
|
||||
try {
|
||||
const clarityV = claritySync.getDirty();
|
||||
if (typeof clarityV === 'number' && Number.isFinite(clarityV) && clarityV !== 0) {
|
||||
const clarityFilter =
|
||||
clarityV > 0
|
||||
? (() => {
|
||||
const a = (clarityV / 10) * 0.8;
|
||||
return Skia.ImageFilter.MakeMatrixConvolution(
|
||||
3,
|
||||
3,
|
||||
[0, -a, 0, -a, 1 + 4 * a, -a, 0, -a, 0],
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
T_CLAMP,
|
||||
true,
|
||||
null
|
||||
);
|
||||
})()
|
||||
: Skia.ImageFilter.MakeBlur(
|
||||
Math.abs(clarityV / 10) * 4,
|
||||
Math.abs(clarityV / 10) * 4,
|
||||
const softenV = softenSync.getDirty();
|
||||
const cv = typeof clarityV === 'number' && Number.isFinite(clarityV) ? clarityV : 0;
|
||||
const sv = typeof softenV === 'number' && Number.isFinite(softenV) ? softenV : 0;
|
||||
if (cv !== 0 || sv < 0) {
|
||||
const a = (cv / 10) * 0.8;
|
||||
let lensFilter =
|
||||
cv > 0
|
||||
? Skia.ImageFilter.MakeMatrixConvolution(
|
||||
3,
|
||||
3,
|
||||
[0, -a, 0, -a, 1 + 4 * a, -a, 0, -a, 0],
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
T_CLAMP,
|
||||
true,
|
||||
null
|
||||
);
|
||||
if (clarityFilter != null) paint.setImageFilter(clarityFilter);
|
||||
)
|
||||
: cv < 0
|
||||
? Skia.ImageFilter.MakeBlur(
|
||||
Math.abs(cv / 10) * 4,
|
||||
Math.abs(cv / 10) * 4,
|
||||
T_CLAMP,
|
||||
null
|
||||
)
|
||||
: null;
|
||||
if (sv < 0) {
|
||||
lensFilter = Skia.ImageFilter.MakeBlur(
|
||||
Math.abs(sv / 10) * 3,
|
||||
Math.abs(sv / 10) * 3,
|
||||
T_CLAMP,
|
||||
lensFilter
|
||||
);
|
||||
}
|
||||
if (lensFilter != null) paint.setImageFilter(lensFilter);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Clarity filter build failed: ' + e);
|
||||
console.warn('Lens filter build failed: ' + e);
|
||||
}
|
||||
const tone = toneSync.getDirty();
|
||||
const cinema = cinemaSync.getDirty();
|
||||
@@ -1393,7 +1414,11 @@ const Viewfinder = forwardRef<ViewfinderHandle, ViewfinderProps>(function Viewfi
|
||||
[colorMatrixSync, toneSync, cinemaSync, hdfSync, claritySync, toneFailLogged, feedRectSync, frameBeatSync]
|
||||
);
|
||||
|
||||
const grainOpacity = adjustments.grain / 20;
|
||||
// NOISE REDUCTION below 0 puts grain back instead of smoothing it away, so a
|
||||
// negative NR and GRAIN share one overlay (capped at 10 → 0.5), exactly as
|
||||
// the export pass does.
|
||||
const grainOpacity =
|
||||
Math.min(10, Math.max(0, adjustments.grain) + Math.max(0, -(adjustments.denoise ?? 0))) / 20;
|
||||
|
||||
// Library-still viewer: pinch-zoom (2 fingers) + double-tap zoom at the tap
|
||||
// point / double-tap again to reset to 100%. Transform is a Skia matrix
|
||||
@@ -2844,10 +2869,16 @@ const Viewfinder = forwardRef<ViewfinderHandle, ViewfinderProps>(function Viewfi
|
||||
<SkiaImage image={skiaImage} fit={fit} rect={rectNode} />
|
||||
);
|
||||
// Mist (negative clarity) = export pass 4's blur; sigma in original px.
|
||||
if (libClarity < 0) {
|
||||
// Sharpening < 0 rides the same wrap: the export runs a second Gaussian
|
||||
// after the mist, and two Gaussians in series are one of sqrt(a^2 + b^2),
|
||||
// so a single pass with that sigma matches the file.
|
||||
const libMist = libClarity < 0 ? Math.abs(libClarity / 10) * 4 * sx : 0;
|
||||
const libSoften = (Math.max(0, -(adjustments.sharpening ?? 0)) / 10) * 3 * sx;
|
||||
const libBlur = Math.sqrt(libMist * libMist + libSoften * libSoften);
|
||||
if (libBlur > 0) {
|
||||
photo = (
|
||||
<Group>
|
||||
<Blur blur={Math.abs(libClarity / 10) * 4 * sx} mode="clamp" />
|
||||
<Blur blur={libBlur} mode="clamp" />
|
||||
{photo}
|
||||
</Group>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user