diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java index f5140eb2..df6c2507 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java @@ -1189,6 +1189,10 @@ public class CameraView extends FrameLayout implements LifecycleObserver { mCameraEngine.setHdr(hdr); } + public boolean isHDRSupported() { + return mCameraEngine.isHDRSupported(); + } + /** * Gets the current hdr value. * @return the current hdr value @@ -1312,6 +1316,10 @@ public class CameraView extends FrameLayout implements LifecycleObserver { mCameraEngine.setFlash(flash); } + public boolean isFlashModeSupported() { + return mCameraEngine.isFlashModeSupported(); + } + /** * Gets the current flash mode. * @return a flash mode @@ -2178,6 +2186,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver { public boolean getDrawHardwareOverlays() { return mOverlayLayout.getHardwareCanvasEnabled(); } + //endregion //region Callbacks and dispatching diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java index 6f976f08..67e32352 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java @@ -515,6 +515,11 @@ public class Camera1Engine extends CameraBaseEngine implements } } + @Override + public boolean isFlashModeSupported() { + return mCameraOptions != null && mCameraOptions.getSupportedFlash().size() > 1; + } + @Override public void setFlash(@NonNull Flash flash) { final Flash old = mFlash; @@ -595,6 +600,10 @@ public class Camera1Engine extends CameraBaseEngine implements mWhiteBalance = oldWhiteBalance; return false; } + @Override + public boolean isHDRSupported() { + return mCameraOptions != null && mCameraOptions.getSupportedHdr().size() > 1; + } @Override public void setHdr(@NonNull Hdr hdr) { diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java index 0f14f0ec..10ecf78a 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java @@ -85,7 +85,8 @@ public class Camera2Engine extends CameraBaseEngine implements ActionHolder { private static final int FRAME_PROCESSING_FORMAT = ImageFormat.YUV_420_888; - @VisibleForTesting static final long METER_TIMEOUT = 5000; + @VisibleForTesting + static final long METER_TIMEOUT = 5000; private static final long METER_TIMEOUT_SHORT = 2500; private final CameraManager mManager; @@ -135,8 +136,8 @@ public class Camera2Engine extends CameraBaseEngine implements @NonNull private T readCharacteristic(@NonNull CameraCharacteristics characteristics, - @NonNull CameraCharacteristics.Key key, - @NonNull T fallback) { + @NonNull CameraCharacteristics.Key key, + @NonNull T fallback) { T value = characteristics.get(key); return value == null ? fallback : value; } @@ -232,6 +233,7 @@ public class Camera2Engine extends CameraBaseEngine implements /** * Can be changed to select something different than {@link CameraDevice#TEMPLATE_PREVIEW} * for the default repeating request. + * * @return the default template for preview */ protected int getRepeatingRequestDefaultTemplate() { @@ -241,7 +243,7 @@ public class Camera2Engine extends CameraBaseEngine implements /** * Applies the repeating request builder to the preview, assuming we actually have a preview * running. Can be called after changing parameters to the builder. - * + *

* To apply a new builder (for example switch between TEMPLATE_PREVIEW and TEMPLATE_RECORD) * it should be set before calling this method, for example by calling * {@link #createRepeatingRequestBuilder(int)}. @@ -423,10 +425,15 @@ public class Camera2Engine extends CameraBaseEngine implements boolean flip = getAngles().flip(Reference.SENSOR, Reference.VIEW); int format; switch (mPictureFormat) { - case JPEG: format = ImageFormat.JPEG; break; - case DNG: format = ImageFormat.RAW_SENSOR; break; - default: throw new IllegalArgumentException("Unknown format:" - + mPictureFormat); + case JPEG: + format = ImageFormat.JPEG; + break; + case DNG: + format = ImageFormat.RAW_SENSOR; + break; + default: + throw new IllegalArgumentException("Unknown format:" + + mPictureFormat); } mCameraOptions = new Camera2Options(mManager, mCameraId, flip, format); createRepeatingRequestBuilder(getRepeatingRequestDefaultTemplate()); @@ -541,9 +548,14 @@ public class Camera2Engine extends CameraBaseEngine implements if (getMode() == Mode.PICTURE) { int format; switch (mPictureFormat) { - case JPEG: format = ImageFormat.JPEG; break; - case DNG: format = ImageFormat.RAW_SENSOR; break; - default: throw new IllegalArgumentException("Unknown format:" + mPictureFormat); + case JPEG: + format = ImageFormat.JPEG; + break; + case DNG: + format = ImageFormat.RAW_SENSOR; + break; + default: + throw new IllegalArgumentException("Unknown format:" + mPictureFormat); } mPictureReader = ImageReader.newInstance( mCaptureSize.getWidth(), @@ -644,11 +656,11 @@ public class Camera2Engine extends CameraBaseEngine implements mFullVideoPendingStub = null; getOrchestrator().scheduleStateful("do take video", CameraState.PREVIEW, new Runnable() { - @Override - public void run() { - doTakeVideo(stub); - } - }); + @Override + public void run() { + doTakeVideo(stub); + } + }); } // Wait for the first frame. @@ -875,11 +887,11 @@ public class Camera2Engine extends CameraBaseEngine implements getOrchestrator().scheduleStateful("reset metering after picture", CameraState.PREVIEW, new Runnable() { - @Override - public void run() { - unlockAndResetMetering(); - } - }); + @Override + public void run() { + unlockAndResetMetering(); + } + }); } } @@ -963,7 +975,10 @@ public class Camera2Engine extends CameraBaseEngine implements LOG.w("Applying the Issue549 workaround.", Thread.currentThread()); maybeRestorePreviewTemplateAfterVideo(); LOG.w("Applied the Issue549 workaround. Sleeping..."); - try { Thread.sleep(600); } catch (InterruptedException ignore) {} + try { + Thread.sleep(600); + } catch (InterruptedException ignore) { + } LOG.w("Applied the Issue549 workaround. Slept!"); } } @@ -976,18 +991,18 @@ public class Camera2Engine extends CameraBaseEngine implements // torn down in the engine thread while this was still being called. getOrchestrator().scheduleStateful("restore preview template", CameraState.BIND, new Runnable() { - @Override - public void run() { - maybeRestorePreviewTemplateAfterVideo(); - } - }); + @Override + public void run() { + maybeRestorePreviewTemplateAfterVideo(); + } + }); } /** * Video recorders might change the camera template to {@link CameraDevice#TEMPLATE_RECORD}. * After the video is taken, we should restore the template preview, which also means that * we'll remove any extra surface target that was added by the video recorder. - * + *

* This method avoids doing this twice by checking the request tag, as set by * the {@link #createRepeatingRequestBuilder(int)} method. */ @@ -1043,7 +1058,9 @@ public class Camera2Engine extends CameraBaseEngine implements int[] modesArray = readCharacteristic(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES, new int[]{}); List modes = new ArrayList<>(); - for (int mode : modesArray) { modes.add(mode); } + for (int mode : modesArray) { + modes.add(mode); + } if (getMode() == Mode.VIDEO && modes.contains(CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO)) { builder.set(CaptureRequest.CONTROL_AF_MODE, @@ -1083,7 +1100,9 @@ public class Camera2Engine extends CameraBaseEngine implements int[] modesArray = readCharacteristic(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES, new int[]{}); List modes = new ArrayList<>(); - for (int mode : modesArray) { modes.add(mode); } + for (int mode : modesArray) { + modes.add(mode); + } if (modes.contains(CaptureRequest.CONTROL_AF_MODE_AUTO)) { builder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO); return; @@ -1103,6 +1122,16 @@ public class Camera2Engine extends CameraBaseEngine implements } } + @Override + public boolean isFlashModeSupported() { + return mCameraOptions.getSupportedFlash().size() > 1; + } + + @Override + public boolean isHDRSupported() { + return mCameraOptions.getSupportedHdr().size() > 1; + } + @Override public void setFlash(@NonNull final Flash flash) { final Flash old = mFlash; @@ -1110,31 +1139,31 @@ public class Camera2Engine extends CameraBaseEngine implements mFlashTask = getOrchestrator().scheduleStateful("flash (" + flash + ")", CameraState.ENGINE, new Runnable() { - @Override - public void run() { - boolean shouldApply = applyFlash(mRepeatingRequestBuilder, old); - boolean needsWorkaround = getState() == CameraState.PREVIEW; - if (needsWorkaround) { - // Runtime changes to the flash value are not correctly handled by the - // driver. See https://stackoverflow.com/q/53003383/4288782 for example. - // For this reason, we go back to OFF, capture once, then go to the new one. - mFlash = Flash.OFF; - applyFlash(mRepeatingRequestBuilder, old); - try { - mSession.capture(mRepeatingRequestBuilder.build(), null, - null); - } catch (CameraAccessException e) { - throw createCameraException(e); + @Override + public void run() { + boolean shouldApply = applyFlash(mRepeatingRequestBuilder, old); + boolean needsWorkaround = getState() == CameraState.PREVIEW; + if (needsWorkaround) { + // Runtime changes to the flash value are not correctly handled by the + // driver. See https://stackoverflow.com/q/53003383/4288782 for example. + // For this reason, we go back to OFF, capture once, then go to the new one. + mFlash = Flash.OFF; + applyFlash(mRepeatingRequestBuilder, old); + try { + mSession.capture(mRepeatingRequestBuilder.build(), null, + null); + } catch (CameraAccessException e) { + throw createCameraException(e); + } + mFlash = flash; + applyFlash(mRepeatingRequestBuilder, old); + applyRepeatingRequestBuilder(); + + } else if (shouldApply) { + applyRepeatingRequestBuilder(); + } } - mFlash = flash; - applyFlash(mRepeatingRequestBuilder, old); - applyRepeatingRequestBuilder(); - - } else if (shouldApply) { - applyRepeatingRequestBuilder(); - } - } - }); + }); } /** @@ -1142,14 +1171,14 @@ public class Camera2Engine extends CameraBaseEngine implements * - {@link CaptureRequest#CONTROL_AE_MODE_ON} * - {@link CaptureRequest#CONTROL_AE_MODE_ON_AUTO_FLASH} * - {@link CaptureRequest#CONTROL_AE_MODE_ON_ALWAYS_FLASH} - * + *

* The API offers a high level control through {@link CaptureRequest#CONTROL_AE_MODE}, * which is what the mapper looks at. It will trigger (if specified) flash only for * still captures which is exactly what we want. - * + *

* However, we set CONTROL_AE_MODE to ON/OFF (depending * on which is available) with both {@link Flash#OFF} and {@link Flash#TORCH}. - * + *

* When CONTROL_AE_MODE is ON or OFF, the low level control, called * {@link CaptureRequest#FLASH_MODE}, becomes effective, and that's where we can actually * distinguish between a turned off flash and a torch flash. @@ -1161,7 +1190,9 @@ public class Camera2Engine extends CameraBaseEngine implements int[] availableAeModesArray = readCharacteristic( CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES, new int[]{}); List availableAeModes = new ArrayList<>(); - for (int mode : availableAeModesArray) { availableAeModes.add(mode); } + for (int mode : availableAeModesArray) { + availableAeModes.add(mode); + } List> pairs = mMapper.mapFlash(mFlash); for (Pair pair : pairs) { @@ -1185,13 +1216,13 @@ public class Camera2Engine extends CameraBaseEngine implements mLocationTask = getOrchestrator().scheduleStateful("location", CameraState.ENGINE, new Runnable() { - @Override - public void run() { - if (applyLocation(mRepeatingRequestBuilder, old)) { - applyRepeatingRequestBuilder(); - } - } - }); + @Override + public void run() { + if (applyLocation(mRepeatingRequestBuilder, old)) { + applyRepeatingRequestBuilder(); + } + } + }); } @SuppressWarnings("WeakerAccess") @@ -1211,13 +1242,13 @@ public class Camera2Engine extends CameraBaseEngine implements "white balance (" + whiteBalance + ")", CameraState.ENGINE, new Runnable() { - @Override - public void run() { - if (applyWhiteBalance(mRepeatingRequestBuilder, old)) { - applyRepeatingRequestBuilder(); - } - } - }); + @Override + public void run() { + if (applyWhiteBalance(mRepeatingRequestBuilder, old)) { + applyRepeatingRequestBuilder(); + } + } + }); } @SuppressWarnings("WeakerAccess") @@ -1239,13 +1270,13 @@ public class Camera2Engine extends CameraBaseEngine implements mHdrTask = getOrchestrator().scheduleStateful("hdr (" + hdr + ")", CameraState.ENGINE, new Runnable() { - @Override - public void run() { - if (applyHdr(mRepeatingRequestBuilder, old)) { - applyRepeatingRequestBuilder(); - } - } - }); + @Override + public void run() { + if (applyHdr(mRepeatingRequestBuilder, old)) { + applyRepeatingRequestBuilder(); + } + } + }); } @SuppressWarnings("WeakerAccess") @@ -1269,16 +1300,16 @@ public class Camera2Engine extends CameraBaseEngine implements "zoom", CameraState.ENGINE, new Runnable() { - @Override - public void run() { - if (applyZoom(mRepeatingRequestBuilder, old)) { - applyRepeatingRequestBuilder(); - if (notify) { - getCallback().dispatchOnZoomChanged(zoom, points); + @Override + public void run() { + if (applyZoom(mRepeatingRequestBuilder, old)) { + applyRepeatingRequestBuilder(); + if (notify) { + getCallback().dispatchOnZoomChanged(zoom, points); + } + } } - } - } - }); + }); } @SuppressWarnings("WeakerAccess") @@ -1327,16 +1358,16 @@ public class Camera2Engine extends CameraBaseEngine implements "exposure correction", CameraState.ENGINE, new Runnable() { - @Override - public void run() { - if (applyExposureCorrection(mRepeatingRequestBuilder, old)) { - applyRepeatingRequestBuilder(); - if (notify) { - getCallback().dispatchOnExposureCorrectionChanged(EVvalue, bounds, points); + @Override + public void run() { + if (applyExposureCorrection(mRepeatingRequestBuilder, old)) { + applyRepeatingRequestBuilder(); + if (notify) { + getCallback().dispatchOnExposureCorrectionChanged(EVvalue, bounds, points); + } + } } - } - } - }); + }); } @SuppressWarnings("WeakerAccess") @@ -1369,13 +1400,13 @@ public class Camera2Engine extends CameraBaseEngine implements "preview fps (" + previewFrameRate + ")", CameraState.ENGINE, new Runnable() { - @Override - public void run() { - if (applyPreviewFrameRate(mRepeatingRequestBuilder, oldPreviewFrameRate)) { - applyRepeatingRequestBuilder(); - } - } - }); + @Override + public void run() { + if (applyPreviewFrameRate(mRepeatingRequestBuilder, oldPreviewFrameRate)) { + applyRepeatingRequestBuilder(); + } + } + }); } @SuppressWarnings("WeakerAccess") @@ -1448,11 +1479,11 @@ public class Camera2Engine extends CameraBaseEngine implements getOrchestrator().scheduleStateful("picture format (" + pictureFormat + ")", CameraState.ENGINE, new Runnable() { - @Override - public void run() { - restart(); - } - }); + @Override + public void run() { + restart(); + } + }); } } @@ -1473,7 +1504,8 @@ public class Camera2Engine extends CameraBaseEngine implements Image image = null; try { image = reader.acquireLatestImage(); - } catch (Exception ignore) { } + } catch (Exception ignore) { + } if (image == null) { LOG.w("onImageAvailable:", "failed to acquire Image!"); } else if (getState() == CameraState.PREVIEW && !isChangingState()) { @@ -1499,21 +1531,21 @@ public class Camera2Engine extends CameraBaseEngine implements // the preview. If the value is changed between the two, the preview step can crash. getOrchestrator().schedule("has frame processors (" + hasFrameProcessors + ")", true, new Runnable() { - @Override - public void run() { - if (getState().isAtLeast(CameraState.BIND) && isChangingState()) { - // Extremely rare case in which this was called in between startBind and - // startPreview. This can cause issues. Try later. - setHasFrameProcessors(hasFrameProcessors); - return; - } - // Apply and restart. - mHasFrameProcessors = hasFrameProcessors; - if (getState().isAtLeast(CameraState.BIND)) { - restartBind(); - } - } - }); + @Override + public void run() { + if (getState().isAtLeast(CameraState.BIND) && isChangingState()) { + // Extremely rare case in which this was called in between startBind and + // startPreview. This can cause issues. Try later. + setHasFrameProcessors(hasFrameProcessors); + return; + } + // Apply and restart. + mHasFrameProcessors = hasFrameProcessors; + if (getState().isAtLeast(CameraState.BIND)) { + restartBind(); + } + } + }); } @Override @@ -1524,20 +1556,20 @@ public class Camera2Engine extends CameraBaseEngine implements // If the value is changed between the two, the preview step can crash. getOrchestrator().schedule("frame processing format (" + format + ")", true, new Runnable() { - @Override - public void run() { - if (getState().isAtLeast(CameraState.BIND) && isChangingState()) { - // Extremely rare case in which this was called in between startBind and - // startPreview. This can cause issues. Try later. - setFrameProcessingFormat(format); - return; - } - mFrameProcessingFormat = format > 0 ? format : FRAME_PROCESSING_FORMAT; - if (getState().isAtLeast(CameraState.BIND)) { - restartBind(); - } - } - }); + @Override + public void run() { + if (getState().isAtLeast(CameraState.BIND) && isChangingState()) { + // Extremely rare case in which this was called in between startBind and + // startPreview. This can cause issues. Try later. + setFrameProcessingFormat(format); + return; + } + mFrameProcessingFormat = format > 0 ? format : FRAME_PROCESSING_FORMAT; + if (getState().isAtLeast(CameraState.BIND)) { + restartBind(); + } + } + }); } //endregion @@ -1554,38 +1586,38 @@ public class Camera2Engine extends CameraBaseEngine implements getOrchestrator().scheduleStateful("autofocus (" + gesture + ")", CameraState.PREVIEW, new Runnable() { - @Override - public void run() { - // The camera options API still has the auto focus API but it really - // refers to "3A metering to a specific point". Since we have a point, check. - if (!mCameraOptions.isAutoFocusSupported()) return; - - // Create the meter and start. - getCallback().dispatchOnFocusStart(gesture, legacyPoint); - final MeterAction action = createMeterAction(regions); - Action wrapper = Actions.timeout(METER_TIMEOUT, action); - wrapper.start(Camera2Engine.this); - wrapper.addCallback(new CompletionCallback() { @Override - protected void onActionCompleted(@NonNull Action a) { - getCallback().dispatchOnFocusEnd(gesture, - action.isSuccessful(), legacyPoint); - getOrchestrator().remove("reset metering"); - if (shouldResetAutoFocus()) { - getOrchestrator().scheduleStatefulDelayed("reset metering", - CameraState.PREVIEW, - getAutoFocusResetDelay(), - new Runnable() { - @Override - public void run() { - unlockAndResetMetering(); + public void run() { + // The camera options API still has the auto focus API but it really + // refers to "3A metering to a specific point". Since we have a point, check. + if (!mCameraOptions.isAutoFocusSupported()) return; + + // Create the meter and start. + getCallback().dispatchOnFocusStart(gesture, legacyPoint); + final MeterAction action = createMeterAction(regions); + Action wrapper = Actions.timeout(METER_TIMEOUT, action); + wrapper.start(Camera2Engine.this); + wrapper.addCallback(new CompletionCallback() { + @Override + protected void onActionCompleted(@NonNull Action a) { + getCallback().dispatchOnFocusEnd(gesture, + action.isSuccessful(), legacyPoint); + getOrchestrator().remove("reset metering"); + if (shouldResetAutoFocus()) { + getOrchestrator().scheduleStatefulDelayed("reset metering", + CameraState.PREVIEW, + getAutoFocusResetDelay(), + new Runnable() { + @Override + public void run() { + unlockAndResetMetering(); + } + }); } - }); - } + } + }); } }); - } - }); } @NonNull diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java index 0614789d..e7f1ccc7 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java @@ -679,6 +679,9 @@ public abstract class CameraEngine implements public abstract float getExposureCorrectionValue(); public abstract void setFlash(@NonNull Flash flash); + public abstract boolean isFlashModeSupported(); + public abstract boolean isHDRSupported(); + @NonNull public abstract Flash getFlash(); public abstract void setWhiteBalance(@NonNull WhiteBalance whiteBalance); diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filter/BaseFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filter/BaseFilter.java index b7aa2cc7..a048870a 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/filter/BaseFilter.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filter/BaseFilter.java @@ -103,6 +103,11 @@ public abstract class BaseFilter implements Filter { @SuppressWarnings({"unused", "WeakerAccess"}) protected String fragmentTextureCoordinateName = DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME; + + public Size getSize() { + return size; + } + @SuppressWarnings("WeakerAccess") @NonNull protected String createDefaultVertexShader() { diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filter/ContextParameterFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filter/ContextParameterFilter.java new file mode 100644 index 00000000..7c6a8520 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filter/ContextParameterFilter.java @@ -0,0 +1,14 @@ +package com.otaliastudios.cameraview.filter; + +import android.content.Context; + +public interface ContextParameterFilter { + /** + * Sets the parameter. + * The value should always be between 0 and 1. + * + * @param value parameter + */ + void setContext(Context value); + +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filter/Filters.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filter/Filters.java index a0c279db..df96f0b9 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/filter/Filters.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filter/Filters.java @@ -4,18 +4,28 @@ import androidx.annotation.NonNull; import com.otaliastudios.cameraview.filters.AutoFixFilter; import com.otaliastudios.cameraview.filters.BlackAndWhiteFilter; +import com.otaliastudios.cameraview.filters.ToasterFilter; import com.otaliastudios.cameraview.filters.BrightnessFilter; +import com.otaliastudios.cameraview.filters.ChromaticAberrationFilter; +import com.otaliastudios.cameraview.filters.ColorspaceFilter; import com.otaliastudios.cameraview.filters.ContrastFilter; import com.otaliastudios.cameraview.filters.CrossProcessFilter; import com.otaliastudios.cameraview.filters.DocumentaryFilter; import com.otaliastudios.cameraview.filters.DuotoneFilter; +import com.otaliastudios.cameraview.filters.EMInterferenceFilter; +import com.otaliastudios.cameraview.filters.EarlybirdFilter; +import com.otaliastudios.cameraview.filters.EdgeDetectionFilter; +import com.otaliastudios.cameraview.filters.ExposureFilter; import com.otaliastudios.cameraview.filters.FillLightFilter; import com.otaliastudios.cameraview.filters.GammaFilter; import com.otaliastudios.cameraview.filters.GrainFilter; import com.otaliastudios.cameraview.filters.GrayscaleFilter; +import com.otaliastudios.cameraview.filters.HalftoneFilter; +import com.otaliastudios.cameraview.filters.HighlightShadowFilter; import com.otaliastudios.cameraview.filters.HueFilter; import com.otaliastudios.cameraview.filters.InvertColorsFilter; import com.otaliastudios.cameraview.filters.LomoishFilter; +import com.otaliastudios.cameraview.filters.LuminanceThresholdFilter; import com.otaliastudios.cameraview.filters.PosterizeFilter; import com.otaliastudios.cameraview.filters.SaturationFilter; import com.otaliastudios.cameraview.filters.SepiaFilter; @@ -23,6 +33,7 @@ import com.otaliastudios.cameraview.filters.SharpnessFilter; import com.otaliastudios.cameraview.filters.TemperatureFilter; import com.otaliastudios.cameraview.filters.TintFilter; import com.otaliastudios.cameraview.filters.VignetteFilter; +import com.otaliastudios.cameraview.filters.WavesReflectionFilter; /** * Contains commonly used {@link Filter}s. @@ -96,7 +107,29 @@ public enum Filters { TINT(TintFilter.class), /** @see VignetteFilter */ - VIGNETTE(VignetteFilter.class); + VIGNETTE(VignetteFilter.class), + + COLOR_SPACE(ColorspaceFilter.class), + + EXPOSURE(ExposureFilter.class), + + HIGHLIGHT_SHADOW(HighlightShadowFilter.class), + + HALF_TONE(HalftoneFilter.class), + + LUMINANCE_THRESHOLD(LuminanceThresholdFilter.class), + + WAVES_REFLECTION(WavesReflectionFilter.class), + + EM_INTERFERENCE(EMInterferenceFilter.class), + + CHROMATIC_ABERRATION(ChromaticAberrationFilter.class), + + EDGE_DETECTION(EdgeDetectionFilter.class), + + EARLY_BIRD(EarlybirdFilter.class), + + BRANNAN(ToasterFilter.class); private Class filterClass; diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/AirsolidGlitchFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/AirsolidGlitchFilter.java new file mode 100644 index 00000000..d8072ccc --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/AirsolidGlitchFilter.java @@ -0,0 +1,140 @@ +package com.otaliastudios.cameraview.filters; + +import android.opengl.GLES20; + +import androidx.annotation.NonNull; + +import com.otaliastudios.cameraview.filter.BaseFilter; +import com.otaliastudios.cameraview.size.Size; +import com.otaliastudios.opengl.core.Egloo; + +import java.nio.FloatBuffer; + +public class AirsolidGlitchFilter extends BaseFilter { + private static final String FRAGMENT_SHADER = + "#extension GL_OES_EGL_image_external : require\n" + + "precision highp float;\n" + + "\n" + + "uniform vec3 iResolution;\n" + + "uniform float iTime;\n" + + "uniform samplerExternalOES iChannel0;\n" + + "varying vec2 " + DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME + ";\n" + + "float rand(vec2 p)\n" + + "{\n" + + " float t = floor(iTime * 50.) / 10.; //speed \n" + + " return fract(sin(dot(p, vec2(t * 12.9898, t * 78.233))) * 43758.5453);\n" + + "}\n" + + "\n" + + "float noise(vec2 uv, float blockiness)\n" + + "{ \n" + + " vec2 lv = fract(uv);\n" + + " vec2 id = floor(uv);\n" + + " \n" + + " float n1 = rand(id);\n" + + " float n2 = rand(id+vec2(1,0));\n" + + " float n3 = rand(id+vec2(0,1));\n" + + " float n4 = rand(id+vec2(1,1));\n" + + " \n" + + " vec2 u = smoothstep(0.0, 1.0 + blockiness, lv);\n" + + "\n" + + " return mix(mix(n1, n2, u.x), mix(n3, n4, u.x), u.y);\n" + + "}\n" + + "\n" + + "float fbm(vec2 uv, int count, float blockiness, float complexity)\n" + + "{\n" + + " float val = 0.0;\n" + + " float amp = 0.5;\n" + + " \n" + + " while(count != 0)\n" + + " {\n" + + " \tval += amp * noise(uv, blockiness);\n" + + " amp *= 0.5;\n" + + " uv *= complexity; \n" + + " count--;\n" + + " }\n" + + " \n" + + " return val;\n" + + "}\n" + + "\n" + + "const float glitchAmplitude = 0.9; // increase this\n" + + "const float glitchNarrowness = 400.0;\n" + + "const float glitchBlockiness = 10000.0;\n" + + "const float glitchMinimizer = 10.0; // decrease this\n" + + "\n" + + "void mainImage( out vec4 fragColor, in vec2 fragCoord )\n" + + "{\n" + + " // Normalized pixel coordinates (from 0 to 1)\n" + + " vec2 uv = fragCoord/iResolution.xy;\n" + + " vec2 a = vec2(uv.x * (iResolution.x / iResolution.y), uv.y);\n" + + " vec2 uv2 = vec2(a.x / iResolution.x, exp(a.y));\n" + + "\tvec2 id = floor(uv * 20.0); // size noise\n" + + " //id.x /= floor(texture(iChannel0, vec2(id / 8.0)).r * 8.0);\n" + + "\n" + + " // Generate shift amplitude\n" + + " float shift = glitchAmplitude * pow(fbm(uv2, int(rand(id) * 6.), glitchBlockiness, glitchNarrowness), glitchMinimizer);\n" + + " \n" + + " // Create a scanline effect\n" + + " float scanline = abs(cos(uv.y * 400.)); //scanline intensite\n" + + " scanline = smoothstep(0.0, 2.0, scanline);\n" + + " shift = smoothstep(0.00001, 0.2, shift);\n" + + " \n" + + " // Apply glitch and RGB shift\n" + + " float colR = texture2D(iChannel0, vec2(uv.x + shift, uv.y)).r * (1. - shift) ;\n" + + " float colG = texture2D(iChannel0, vec2(uv.x - shift, uv.y)).g * (1. - shift) + rand(id) * shift;\n" + + " float colB = texture2D(iChannel0, vec2(uv.x - shift, uv.y)).b * (1. - shift);\n" + + " // Mix with the scanline effect\n" + + " vec3 f = vec3(colR, colG, colB) - (0.1 * scanline); //scanline intensite\n" + + " \n" + + " // Output to screen\n" + + " fragColor = vec4(f, 1.0);\n" + + "}" + + "\nvoid main() {\n" + + "\tmainImage(gl_FragColor, " + DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME + " * iResolution.xy);\n" + + "}"; + + long START_TIME = System.currentTimeMillis(); + private int iGlobalTimeLocation = -1; + private int iResolutionLocation = -1; + + @NonNull + @Override + public String getFragmentShader() { + return FRAGMENT_SHADER; + } + + @Override + public void onDestroy() { + super.onDestroy(); + iResolutionLocation = -1; + iGlobalTimeLocation = -1; + START_TIME = System.currentTimeMillis(); + } + + @Override + public void onCreate(int programHandle) { + super.onCreate(programHandle); + iResolutionLocation = GLES20.glGetUniformLocation(programHandle, "iResolution"); + Egloo.checkGlProgramLocation(iResolutionLocation, "iResolution"); + + + iGlobalTimeLocation = GLES20.glGetUniformLocation(programHandle, "iTime"); + Egloo.checkGlProgramLocation(iGlobalTimeLocation, "iTime"); + } + + @Override + protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) { + super.onPreDraw(timestampUs, transformMatrix); + + Size size = getSize(); + if (size != null) { + GLES20.glUniform3fv(iResolutionLocation, 1, + FloatBuffer.wrap(new float[]{(float) size.getWidth(), (float) size.getHeight(), 1.0f})); + Egloo.checkGlError("glUniform3fv"); + } + + + float time = (((float) (System.currentTimeMillis() - START_TIME)) / 1000.0f) + 1; + GLES20.glUniform1f(iGlobalTimeLocation, time); + Egloo.checkGlError("glUniform1f"); + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/AnaglyphFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/AnaglyphFilter.java new file mode 100644 index 00000000..fa6fbf18 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/AnaglyphFilter.java @@ -0,0 +1,81 @@ +package com.otaliastudios.cameraview.filters; + +import android.opengl.GLES20; + +import androidx.annotation.NonNull; + +import com.otaliastudios.cameraview.filter.BaseFilter; +import com.otaliastudios.cameraview.size.Size; +import com.otaliastudios.opengl.core.Egloo; + +import java.nio.FloatBuffer; + +public class AnaglyphFilter extends BaseFilter { + private static final String FRAGMENT_SHADER = + "#extension GL_OES_EGL_image_external : require\n" + + "precision highp float;\n" + + "\n" + + "uniform vec3 iResolution;\n" + + "uniform float iTime;\n" + + "uniform samplerExternalOES iChannel0;\n" + + "varying vec2 " + DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME + ";\n" + + "void mainImage( out vec4 fragColor, in vec2 fragCoord )\n" + + "{\n" + + "\tvec2 uv = fragCoord.xy / iResolution.xy;\n" + + " \n" + + " vec4 left = texture2D(iChannel0, uv);\n" + + " vec4 right = texture2D(iChannel0, uv + vec2(0.015, 0.0));\n" + + "\t\n" + + " vec3 color = vec3(left.r, right.gb);\n" + + " color = clamp(color, 0.0, 1.0);\n" + + " fragColor = vec4(color, 1.0);\n" + + "}\n" + + "void main() {\n" + + "\tmainImage(gl_FragColor, " + DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME + " * iResolution.xy);\n" + + "}"; + + + long START_TIME = System.currentTimeMillis(); + private int iGlobalTimeLocation = -1; + private int iResolutionLocation = -1; + + @NonNull + @Override + public String getFragmentShader() { + return FRAGMENT_SHADER; + } + + @Override + public void onDestroy() { + super.onDestroy(); + iResolutionLocation = -1; + iGlobalTimeLocation = -1; + START_TIME = System.currentTimeMillis(); + } + + @Override + public void onCreate(int programHandle) { + super.onCreate(programHandle); + iResolutionLocation = GLES20.glGetUniformLocation(programHandle, "iResolution"); + Egloo.checkGlProgramLocation(iResolutionLocation, "iResolution"); + + iGlobalTimeLocation = GLES20.glGetUniformLocation(programHandle, "iTime"); +// Egloo.checkGlProgramLocation(iGlobalTimeLocation, "iTime"); + } + + @Override + protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) { + super.onPreDraw(timestampUs, transformMatrix); + + Size size = getSize(); + if (size != null) { + GLES20.glUniform3fv(iResolutionLocation, 1, + FloatBuffer.wrap(new float[]{(float) size.getWidth(), (float) size.getHeight(), 1.0f})); + Egloo.checkGlError("glUniform3fv"); + } + + float time = ((float) (System.currentTimeMillis() - START_TIME)) / 1000.0f; + GLES20.glUniform1f(iGlobalTimeLocation, time); + Egloo.checkGlError("glUniform1f"); + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/AsciiFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/AsciiFilter.java new file mode 100644 index 00000000..5497c049 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/AsciiFilter.java @@ -0,0 +1,93 @@ +package com.otaliastudios.cameraview.filters; + +import android.opengl.GLES20; + +import androidx.annotation.NonNull; + +import com.otaliastudios.cameraview.filter.BaseFilter; +import com.otaliastudios.cameraview.size.Size; +import com.otaliastudios.opengl.core.Egloo; + +import java.nio.FloatBuffer; + +public class AsciiFilter extends BaseFilter { + private static final String FRAGMENT_SHADER = + "#extension GL_OES_EGL_image_external : require\n" + + "precision highp float;\n" + + "\n" + + "uniform vec3 iResolution;\n" + + "uniform samplerExternalOES iChannel0;\n" + + "varying vec2 " + DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME + ";\n" + + "float character(int n, vec2 p)\n" + + "{\n" + + "\tp = floor(p*vec2(4.0, -4.0) + 2.5);\n" + + " if (clamp(p.x, 0.0, 4.0) == p.x)\n" + + "\t{\n" + + " if (clamp(p.y, 0.0, 4.0) == p.y)\t\n" + + "\t\t{\n" + + " \tint a = int(round(p.x) + 5.0 * round(p.y));\n" + + "\t\t\tif (((n >> a) & 1) == 1) return 1.0;\n" + + "\t\t}\t\n" + + " }\n" + + "\treturn 0.0;\n" + + "}\n" + + "\n" + + "void mainImage( out vec4 fragColor, in vec2 fragCoord )\n" + + "{\n" + + "\tvec2 pix = fragCoord.xy;\n" + + "\tvec3 col = texture2D(iChannel0, floor(pix/8.0)*8.0/iResolution.xy).rgb;\t\n" + + "\t\n" + + "\tfloat gray = 0.3 * col.r + 0.59 * col.g + 0.11 * col.b;\n" + + "\t\n" + + "\tint n = 4096; // .\n" + + "\tif (gray > 0.2) n = 65600; // :\n" + + "\tif (gray > 0.3) n = 332772; // *\n" + + "\tif (gray > 0.4) n = 15255086; // o \n" + + "\tif (gray > 0.5) n = 23385164; // &\n" + + "\tif (gray > 0.6) n = 15252014; // 8\n" + + "\tif (gray > 0.7) n = 13199452; // @\n" + + "\tif (gray > 0.8) n = 11512810; // #\n" + + "\t\n" + + "\tvec2 p = mod(pix/4.0, 2.0) - vec2(1.0);\n" + + " \n" + + "\tif (iMouse.z > 0.5)\tcol = gray*vec3(character(n, p));\n" + + "\telse col = col*character(n, p);\n" + + "\t\n" + + "\tfragColor = vec4(col, 1.0);\n" + + "}" + + "\nvoid main() {\n" + + "\tmainImage(gl_FragColor, " + DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME + " * iResolution.xy);\n" + + "}"; + private int iResolutionLocation = -1; + + @NonNull + @Override + public String getFragmentShader() { + return FRAGMENT_SHADER; + } + + @Override + public void onDestroy() { + super.onDestroy(); + iResolutionLocation = -1; + } + + @Override + public void onCreate(int programHandle) { + super.onCreate(programHandle); + iResolutionLocation = GLES20.glGetUniformLocation(programHandle, "iResolution"); + Egloo.checkGlProgramLocation(iResolutionLocation, "iResolution"); + } + + @Override + protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) { + super.onPreDraw(timestampUs, transformMatrix); + + Size size = getSize(); + if (size != null) { + GLES20.glUniform3fv(iResolutionLocation, 1, + FloatBuffer.wrap(new float[]{(float) size.getWidth(), (float) size.getHeight(), 1.0f})); + Egloo.checkGlError("glUniform3fv"); + } + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/ChromaticAberrationFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/ChromaticAberrationFilter.java new file mode 100644 index 00000000..14bda471 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/ChromaticAberrationFilter.java @@ -0,0 +1,79 @@ +package com.otaliastudios.cameraview.filters; + +import android.opengl.GLES20; + +import androidx.annotation.NonNull; + +import com.otaliastudios.cameraview.filter.BaseFilter; +import com.otaliastudios.opengl.core.Egloo; + +public class ChromaticAberrationFilter extends BaseFilter { + private static final String FRAGMENT_SHADER = + "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;\n" + + "\n" + + "uniform vec3 iResolution;\n" + + "uniform float iGlobalTime;\n" + + "uniform samplerExternalOES iChannel0;\n" + + "varying vec2 "+DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME+";\n" + + "\n" + + "void mainImage( out vec4 fragColor, in vec2 fragCoord )\n" + + "{\n" + + " vec2 uv = fragCoord.xy;\n" + + "\n" + + "\tfloat amount = 0.0;\n" + + "\t\n" + + "\tamount = (1.0 + sin(iGlobalTime*6.0)) * 0.5;\n" + + "\tamount *= 1.0 + sin(iGlobalTime*16.0) * 0.5;\n" + + "\tamount *= 1.0 + sin(iGlobalTime*19.0) * 0.5;\n" + + "\tamount *= 1.0 + sin(iGlobalTime*27.0) * 0.5;\n" + + "\tamount = pow(amount, 3.0);\n" + + "\n" + + "\tamount *= 0.05;\n" + + "\t\n" + + " vec3 col;\n" + + " col.r = texture2D( iChannel0, vec2(uv.x+amount,uv.y) ).r;\n" + + " col.g = texture2D( iChannel0, uv ).g;\n" + + " col.b = texture2D( iChannel0, vec2(uv.x-amount,uv.y) ).b;\n" + + "\n" + + "\tcol *= (1.0 - amount * 0.5);\n" + + "\t\n" + + " fragColor = vec4(col,1.0);\n" + + "}\n" + + "\n" + + "\n" + + "void main() {\n" + + " \tmainImage(gl_FragColor, "+DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME+");\n" + + "}"; + + long START_TIME = System.currentTimeMillis(); + private int iGlobalTimeLocation = -1; + + @NonNull + @Override + public String getFragmentShader() { + return FRAGMENT_SHADER; + } + + @Override + public void onDestroy() { + super.onDestroy(); + iGlobalTimeLocation = -1; + START_TIME = System.currentTimeMillis(); + } + + @Override + public void onCreate(int programHandle) { + super.onCreate(programHandle); + iGlobalTimeLocation = GLES20.glGetUniformLocation(programHandle, "iGlobalTime"); + Egloo.checkGlProgramLocation(iGlobalTimeLocation, "iGlobalTime"); + } + + @Override + protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) { + super.onPreDraw(timestampUs, transformMatrix); + float time = ((float) (System.currentTimeMillis() - START_TIME)) / 1000.0f; + GLES20.glUniform1f(iGlobalTimeLocation, time); + Egloo.checkGlError("glUniform1f"); + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/ColorspaceFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/ColorspaceFilter.java new file mode 100644 index 00000000..0d0e098d --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/ColorspaceFilter.java @@ -0,0 +1,56 @@ +package com.otaliastudios.cameraview.filters; + +import androidx.annotation.NonNull; + +import com.otaliastudios.cameraview.filter.BaseFilter; +import com.otaliastudios.cameraview.filter.OneParameterFilter; + +public class ColorspaceFilter extends BaseFilter { + private static final String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;\n" + + + "varying vec2 vTextureCoord;" + + "uniform samplerExternalOES sTexture;" + + + "void main() {" + + "highp vec2 sampleDivisor = vec2(1.0 / 200.0, 1.0 / 320.0);" + + + "highp vec2 samplePos = vTextureCoord - mod(vTextureCoord, sampleDivisor);" + + "highp vec4 color = texture2D(sTexture, samplePos);" + + + "mediump vec4 colorCyan = vec4(85.0 / 255.0, 1.0, 1.0, 1.0);" + + "mediump vec4 colorMagenta = vec4(1.0, 85.0 / 255.0, 1.0, 1.0);" + + "mediump vec4 colorWhite = vec4(1.0, 1.0, 1.0, 1.0);" + + "mediump vec4 colorBlack = vec4(0.0, 0.0, 0.0, 1.0);" + + + "mediump vec4 endColor;" + + "highp float blackDistance = distance(color, colorBlack);" + + "highp float whiteDistance = distance(color, colorWhite);" + + "highp float magentaDistance = distance(color, colorMagenta);" + + "highp float cyanDistance = distance(color, colorCyan);" + + + "mediump vec4 finalColor;" + + + "highp float colorDistance = min(magentaDistance, cyanDistance);" + + "colorDistance = min(colorDistance, whiteDistance);" + + "colorDistance = min(colorDistance, blackDistance);" + + + "if (colorDistance == blackDistance) {" + + "finalColor = colorBlack;" + + "} else if (colorDistance == whiteDistance) {" + + "finalColor = colorWhite;" + + "} else if (colorDistance == cyanDistance) {" + + "finalColor = colorCyan;" + + "} else {" + + "finalColor = colorMagenta;" + + "}" + + + "gl_FragColor = finalColor;" + + "}"; + + @NonNull + @Override + public String getFragmentShader() { + return FRAGMENT_SHADER; + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/CrackedFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/CrackedFilter.java new file mode 100644 index 00000000..1fcbeb2f --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/CrackedFilter.java @@ -0,0 +1,109 @@ +package com.otaliastudios.cameraview.filters; + +import android.opengl.GLES20; + +import androidx.annotation.NonNull; + +import com.otaliastudios.cameraview.filter.BaseFilter; +import com.otaliastudios.cameraview.size.Size; +import com.otaliastudios.opengl.core.Egloo; + +import java.nio.FloatBuffer; + +public class CrackedFilter extends BaseFilter { + private static final String FRAGMENT_SHADER = + "#extension GL_OES_EGL_image_external : require\n" + + "#extension GL_OES_standard_derivatives : enable\n" + + "precision highp float;\n" + + "\n" + + "uniform vec3 iResolution;\n" + +// "uniform float iGlobalTime;\n" + + "uniform samplerExternalOES iChannel0;\n" + + "varying vec2 " + DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME + ";\n" + + "\n" + + "float rnd(vec2 s)\n" + + "{\n" + + " return 1.-2.*fract(sin(s.x*253.13+s.y*341.41)*589.19);\n" + + "}\n" + + "\n" + + "void mainImage( out vec4 fragColor, in vec2 fragCoord )\n" + + "{\n" + + "\tvec2 p=(fragCoord.xy*2.-iResolution.xy)/iResolution.x;\n" + + "\n" + + " vec2 v=vec2(1E3);\n" + + " vec2 v2=vec2(1E4);\n" + + " vec2 center=vec2(.1,-.5);\n" + + " for(int c=0;c<30;c++)\n" + + " {\n" + + " float angle=floor(rnd(vec2(float(c),387.44))*16.)*3.1415*.4-.5;\n" + + " float dist=pow(rnd(vec2(float(c),78.21)),2.)*.5;\n" + + " vec2 vc=vec2(center.x+cos(angle)*dist+rnd(vec2(float(c),349.3))*7E-3,\n" + + " center.y+sin(angle)*dist+rnd(vec2(float(c),912.7))*7E-3);\n" + + " if(length(vc-p) 1.4)\n" + + "\t\tfinalColor.xy = mix(finalColor.xy, normalizedCoord3, 0.5);\n" + + "\telse if (length(finalColor) < 0.4)\n" + + "\t\tfinalColor.yz = mix(finalColor.yz, normalizedCoord3, 0.5);\n" + + "\t\t\n" + + "\tfragColor = finalColor;\t\t\n" + + "}\n"+ + "\nvoid main() {\n" + + "\tmainImage(gl_FragColor, " + DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME + " * iResolution.xy);\n" + + "}"; + + long START_TIME = System.currentTimeMillis(); + private int iGlobalTimeLocation = -1; + private int iResolutionLocation = -1; + + @NonNull + @Override + public String getFragmentShader() { + return FRAGMENT_SHADER; + } + + @Override + public void onDestroy() { + super.onDestroy(); + iResolutionLocation = -1; + iGlobalTimeLocation = -1; + START_TIME = System.currentTimeMillis(); + } + + @Override + public void onCreate(int programHandle) { + super.onCreate(programHandle); + iResolutionLocation = GLES20.glGetUniformLocation(programHandle, "iResolution"); + Egloo.checkGlProgramLocation(iResolutionLocation, "iResolution"); + + + iGlobalTimeLocation = GLES20.glGetUniformLocation(programHandle, "iTime"); + Egloo.checkGlProgramLocation(iGlobalTimeLocation, "iTime"); + } + + @Override + protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) { + super.onPreDraw(timestampUs, transformMatrix); + + Size size = getSize(); + if (size != null) { + GLES20.glUniform3fv(iResolutionLocation, 1, + FloatBuffer.wrap(new float[]{(float) size.getWidth(), (float) size.getHeight(), 1.0f})); + Egloo.checkGlError("glUniform3fv"); + } + + + float time = (((float) (System.currentTimeMillis() - START_TIME)) / 1000.0f) + 1; + GLES20.glUniform1f(iGlobalTimeLocation, time); + Egloo.checkGlError("glUniform1f"); + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/EMInterferenceFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/EMInterferenceFilter.java new file mode 100644 index 00000000..c4b75cfb --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/EMInterferenceFilter.java @@ -0,0 +1,83 @@ +package com.otaliastudios.cameraview.filters; + +import android.opengl.GLES20; + +import androidx.annotation.NonNull; + +import com.otaliastudios.cameraview.filter.BaseFilter; +import com.otaliastudios.opengl.core.Egloo; + +import java.nio.FloatBuffer; + +public class EMInterferenceFilter extends BaseFilter { + private static final String FRAGMENT_SHADER = + "#extension GL_OES_EGL_image_external : require\n" + + "precision highp float;\n" + + "\n" + + "uniform float iGlobalTime;\n" + + "uniform samplerExternalOES iChannel0;\n" + + "varying vec2 " + DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME + ";\n" + + "\n" + + "float rng2(vec2 seed)\n" + + "{\n" + + " return fract(sin(dot(seed * floor(iGlobalTime * 12.), vec2(127.1,311.7))) * 43758.5453123);\n" + + "}\n" + + "\n" + + "float rng(float seed)\n" + + "{\n" + + " return rng2(vec2(seed, 1.0));\n" + + "}\n" + + "\n" + + "void mainImage( out vec4 fragColor, in vec2 fragCoord )\n" + + "{\n" + + "\tvec2 uv = fragCoord.xy;\n" + + " vec2 blockS = floor(uv * vec2(24., 9.));\n" + + " vec2 blockL = floor(uv * vec2(8., 4.));\n" + + "\n" + + " float r = rng2(uv);\n" + + " vec3 noise = (vec3(r, 1. - r, r / 2. + 0.5) * 1.0 - 2.0) * 0.08;\n" + + "\n" + + " float lineNoise = pow(rng2(blockS), 8.0) * pow(rng2(blockL), 3.0) - pow(rng(7.2341), 17.0) * 2.;\n" + + "\n" + + " vec4 col1 = texture2D(iChannel0, uv);\n" + + " vec4 col2 = texture2D(iChannel0, uv + vec2(lineNoise * 0.05 * rng(5.0), 0));\n" + + " vec4 col3 = texture2D(iChannel0, uv - vec2(lineNoise * 0.05 * rng(31.0), 0));\n" + + "\n" + + "\tfragColor = vec4(vec3(col1.x, col2.y, col3.z) + noise, 1.0);\n" + + "}\n" + + "\n" + + "void main() {\n" + + "\tmainImage(gl_FragColor, " + DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME + ");\n" + + "}"; + + long START_TIME = System.currentTimeMillis(); + private int iGlobalTimeLocation = -1; + + @NonNull + @Override + public String getFragmentShader() { + return FRAGMENT_SHADER; + } + + @Override + public void onDestroy() { + super.onDestroy(); + iGlobalTimeLocation = -1; + START_TIME = System.currentTimeMillis(); + } + + @Override + public void onCreate(int programHandle) { + super.onCreate(programHandle); + iGlobalTimeLocation = GLES20.glGetUniformLocation(programHandle, "iGlobalTime"); + Egloo.checkGlProgramLocation(iGlobalTimeLocation, "iGlobalTime"); + } + + @Override + protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) { + super.onPreDraw(timestampUs, transformMatrix); + float time = ((float) (System.currentTimeMillis() - START_TIME)) / 1000.0f; + GLES20.glUniform1f(iGlobalTimeLocation, time); + Egloo.checkGlError("glUniform1f"); + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/EarlybirdFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/EarlybirdFilter.java new file mode 100644 index 00000000..401bde59 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/EarlybirdFilter.java @@ -0,0 +1,106 @@ +package com.otaliastudios.cameraview.filters; + +import android.content.Context; +import android.opengl.GLES20; + +import androidx.annotation.NonNull; + +import com.otaliastudios.cameraview.R; +import com.otaliastudios.cameraview.filter.BaseFilter; +import com.otaliastudios.cameraview.filter.ContextParameterFilter; +import com.otaliastudios.cameraview.size.Size; +import com.otaliastudios.opengl.core.Egloo; + +import java.nio.FloatBuffer; + +public class EarlybirdFilter extends BaseFilter implements ContextParameterFilter { + + + private static final String FRAGMENT_SHADER = + "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float; \n" + + "uniform samplerExternalOES sTexture; \n" + + "varying vec2 " + DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME + "; \n" + + "const vec3 W = vec3(0.2125, 0.7154, 0.0721);\n" + + "\n" + + "vec3 BrightnessContrastSaturation(vec3 color, float brt, float con, float sat)\n" + + "{\n" + + "\tvec3 black = vec3(0., 0., 0.);\n" + + "\tvec3 middle = vec3(0.5, 0.5, 0.5);\n" + + "\tfloat luminance = dot(color, W);\n" + + "\tvec3 gray = vec3(luminance, luminance, luminance);\n" + + "\t\n" + + "\tvec3 brtColor = mix(black, color, brt);\n" + + "\tvec3 conColor = mix(middle, brtColor, con);\n" + + "\tvec3 satColor = mix(gray, conColor, sat);\n" + + "\treturn satColor;\n" + + "}\n" + + "\n" + + "vec3 ovelayBlender(vec3 Color, vec3 filter){\n" + + "\tvec3 filter_result;\n" + + "\tfloat luminance = dot(filter, W);\n" + + "\t\n" + + "\tif(luminance < 0.5)\n" + + "\t\tfilter_result = 2. * filter * Color;\n" + + "\telse\n" + + "\t\tfilter_result = 1. - (1. - (2. *(filter - 0.5)))*(1. - Color);\n" + + "\t\t\n" + + "\treturn filter_result;\n" + + "}\n" + + "\n" + + "vec3 multiplyBlender(vec3 Color, vec3 filter){\n" + + "\tvec3 filter_result;\n" + + "\tfloat luminance = dot(filter, W);\n" + + "\t\n" + + "\tif(luminance < 0.5)\n" + + "\t\tfilter_result = 2. * filter * Color;\n" + + "\telse\n" + + "\t\tfilter_result = Color;\n" + + "\t\t\t\n" + + "\treturn filter_result;\n" + + "}\n" + + "\n" + + "void main()\n" + + "{\n" + + "\t //get the pixel\n" + + " vec2 st = " + DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME + ".st;\n" + + " vec3 irgb = texture2D(sTexture, st).rgb;\n" + + " \n" + + " //adjust the brightness/contrast/saturation\n" + + " float T_bright = 1.2;\n" + + " float T_contrast = 1.1;\n" + + " float T_saturation = 1.2;\n" + + " vec3 bcs_result = BrightnessContrastSaturation(irgb, T_bright, T_contrast, T_saturation);\n" + + " \n" + + " //more red, less blue\n" + + " vec3 rb_result = vec3(bcs_result.r*1.1, bcs_result.g, bcs_result.b*0.9);\n" + + " \n" + + " gl_FragColor = vec4(rb_result, 1.);\n" + + "}"; + + + @NonNull + @Override + public String getFragmentShader() { + return FRAGMENT_SHADER; + } + + @Override + public void onDestroy() { + super.onDestroy(); + } + + @Override + public void onCreate(int programHandle) { + super.onCreate(programHandle); + } + + @Override + protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) { + super.onPreDraw(timestampUs, transformMatrix); + } + + @Override + public void setContext(Context value) { + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/EdgeDetectionFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/EdgeDetectionFilter.java new file mode 100644 index 00000000..3651f93e --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/EdgeDetectionFilter.java @@ -0,0 +1,48 @@ +package com.otaliastudios.cameraview.filters; + +import androidx.annotation.NonNull; + +import com.otaliastudios.cameraview.filter.BaseFilter; + +public class EdgeDetectionFilter extends BaseFilter { + private static final String FRAGMENT_SHADER = + "#extension GL_OES_EGL_image_external : require\n" + + "#extension GL_OES_standard_derivatives : enable\n" + + "precision highp float;\n" + + "\n" + + "uniform samplerExternalOES iChannel0;\n" + + "varying vec2 " + DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME + ";\n" + + "\n" + + "void mainImage( out vec4 fragColor, in vec2 fragCoord )\n" + + "{\n" + + " vec2 uv = fragCoord.xy;\n" + + " vec4 color = texture2D(iChannel0, fragCoord);\n" + + " float gray = length(color.rgb);\n" + + " fragColor = vec4(vec3(step(0.06, length(vec2(dFdx(gray), dFdy(gray))))), 1.0);\n" + + "}\n" + + "\n" + + "void main() {\n" + + " mainImage(gl_FragColor, " + DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME + ");\n" + + "}"; + + @NonNull + @Override + public String getFragmentShader() { + return FRAGMENT_SHADER; + } + + @Override + public void onDestroy() { + super.onDestroy(); + } + + @Override + public void onCreate(int programHandle) { + super.onCreate(programHandle); + } + + @Override + protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) { + super.onPreDraw(timestampUs, transformMatrix); + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/EdgeGlowFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/EdgeGlowFilter.java new file mode 100644 index 00000000..cbf016e8 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/EdgeGlowFilter.java @@ -0,0 +1,143 @@ +package com.otaliastudios.cameraview.filters; + +import android.opengl.GLES20; + +import androidx.annotation.NonNull; + +import com.otaliastudios.cameraview.filter.BaseFilter; +import com.otaliastudios.cameraview.size.Size; +import com.otaliastudios.opengl.core.Egloo; + +import java.nio.FloatBuffer; + +public class EdgeGlowFilter extends BaseFilter { + private static final String FRAGMENT_SHADER = + "#extension GL_OES_EGL_image_external : require\n" + + "precision highp float;\n" + + "\n" + + "uniform vec3 iResolution;\n" + + "uniform float iTime;\n" + + "uniform samplerExternalOES iChannel0;\n" + + "varying vec2 " + DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME + ";\n" + + "\n" + + "/* Returns rgb vec from input 0-1 */\n" + + "vec3 getRainbowColor(in float val) {\n" + + " /*convert to rainbow RGB*/\n" + + " float a = (1.0 - val) * 6.0;\n" + + " int X = int(floor(a));\n" + + " float Y = a - float(X);\n" + + " float r = 0.;\n" + + " float g = 0.;\n" + + " float b = 0.;\n" + + " if (X == 0) {\n" + + " r = 1.; g = Y; b = 0.;\n" + + " } else if (X == 1) {\n" + + " r = 1. - Y; g = 1.; b = 0.;\n" + + " } else if (X == 2) {\n" + + " r = 0.; g = 1.; b = Y;\n" + + " } else if (X == 3) {\n" + + " r = 0.; g = 1. - Y; b = 1.;\n" + + " } else if (X == 4) {\n" + + " r = Y; g = 0.; b = 1.;\n" + + " } else if (X == 5) {\n" + + " r = 1.; g = 0.; b = 1. - Y;\n" + + " } else {\n" + + " r = 0.; g = 0.; b = 0.;\n" + + " }\n" + + " return vec3(r, g, b);\n" + + "}\n" + + "\n" + + "float d;\n" + + "\n" + + "float lookup(vec2 p, float dx, float dy, float edgeIntensity)\n" + + "{\n" + + " vec2 uv = (p.xy + vec2(dx * edgeIntensity, dy * edgeIntensity)) / iResolution.xy;\n" + + " vec4 c = texture2D(iChannel0, uv.xy);\n" + + "\t\n" + + "\t// return as luma\n" + + " return 0.2126*c.r + 0.7152*c.g + 0.0722*c.b;\n" + + "}\n" + + "\n" + + "void mainImage( out vec4 fragColor, in vec2 fragCoord )\n" + + "{\n" + + " float timeNorm = mod(iTime, 5.) / 5.;\n" + + " vec3 glowCol = getRainbowColor(timeNorm);\n" + + " float edgeIntensity = 1.;\n" + + " if (timeNorm < .5) { edgeIntensity += (4. * timeNorm);}\n" + + " else { edgeIntensity += -4. * (timeNorm - 1.); }\n" + + " vec2 p = fragCoord.xy;\n" + + " \n" + + "\t// simple sobel edge detection\n" + + " float gx = 0.0;\n" + + " gx += -1.0 * lookup(p, -1.0, -1.0, edgeIntensity);\n" + + " gx += -2.0 * lookup(p, -1.0, 0.0, edgeIntensity);\n" + + " gx += -1.0 * lookup(p, -1.0, 1.0, edgeIntensity);\n" + + " gx += 1.0 * lookup(p, 1.0, -1.0, edgeIntensity);\n" + + " gx += 2.0 * lookup(p, 1.0, 0.0, edgeIntensity);\n" + + " gx += 1.0 * lookup(p, 1.0, 1.0, edgeIntensity);\n" + + " \n" + + " float gy = 0.0;\n" + + " gy += -1.0 * lookup(p, -1.0, -1.0, edgeIntensity);\n" + + " gy += -2.0 * lookup(p, 0.0, -1.0, edgeIntensity);\n" + + " gy += -1.0 * lookup(p, 1.0, -1.0, edgeIntensity);\n" + + " gy += 1.0 * lookup(p, -1.0, 1.0, edgeIntensity);\n" + + " gy += 2.0 * lookup(p, 0.0, 1.0, edgeIntensity);\n" + + " gy += 1.0 * lookup(p, 1.0, 1.0, edgeIntensity);\n" + + " \n" + + "\t// hack: use g^2 to conceal noise in the video\n" + + " float g = gx*gx + gy*gy;\n" + + " \n" + + " vec4 col = texture2D(iChannel0, p / iResolution.xy);\n" + + " col += vec4(g * glowCol, 1.0);\n" + + " \n" + + " fragColor = col;\n" + + "}" + + "\n" + + "void main() {\n" + + "\tmainImage(gl_FragColor, " + DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME + " * iResolution.xy);\n" + + "}"; + + + long START_TIME = System.currentTimeMillis(); + private int iGlobalTimeLocation = -1; + private int iResolutionLocation = -1; + + @NonNull + @Override + public String getFragmentShader() { + return FRAGMENT_SHADER; + } + + @Override + public void onDestroy() { + super.onDestroy(); + iGlobalTimeLocation = -1; + START_TIME = System.currentTimeMillis(); + } + + @Override + public void onCreate(int programHandle) { + super.onCreate(programHandle); + iResolutionLocation = GLES20.glGetUniformLocation(programHandle, "iResolution"); + Egloo.checkGlProgramLocation(iResolutionLocation, "iResolution"); + + iGlobalTimeLocation = GLES20.glGetUniformLocation(programHandle, "iTime"); + Egloo.checkGlProgramLocation(iGlobalTimeLocation, "iTime"); + } + + @Override + protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) { + super.onPreDraw(timestampUs, transformMatrix); + + Size size = getSize(); + if (size != null) { + GLES20.glUniform3fv(iResolutionLocation, 1, + FloatBuffer.wrap(new float[]{(float) size.getWidth(), (float) size.getHeight(), 1.0f})); + Egloo.checkGlError("glUniform3fv"); + } + + float time = ((float) (System.currentTimeMillis() - START_TIME)) / 1000.0f; + GLES20.glUniform1f(iGlobalTimeLocation, time); + Egloo.checkGlError("glUniform1f"); + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/ExposureFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/ExposureFilter.java new file mode 100644 index 00000000..fabe7dea --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/ExposureFilter.java @@ -0,0 +1,77 @@ +package com.otaliastudios.cameraview.filters; + +import android.opengl.GLES20; + +import androidx.annotation.NonNull; + +import com.otaliastudios.cameraview.filter.BaseFilter; +import com.otaliastudios.cameraview.filter.OneParameterFilter; +import com.otaliastudios.cameraview.filter.TwoParameterFilter; +import com.otaliastudios.opengl.core.Egloo; + +/** + * exposure: The adjusted exposure (-1 - 0 - 1 as the default) + */ +public class ExposureFilter extends BaseFilter implements OneParameterFilter { + private static final String EXPOSURE_FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;\n" + + "uniform samplerExternalOES sTexture;\n" + + "uniform float exposure;\n" + + "varying vec2 "+DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME+";\n" + + "void main()\n" + + "{\n" + + " vec4 textureColor = texture2D(sTexture, "+DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME+");\n" + + " gl_FragColor = vec4(textureColor.rgb * pow(2.0, exposure), textureColor.w);\n" + + "}"; + + private float exposure = 1f; + private int exposureLocation = -1; + + @NonNull + @Override + public String getFragmentShader() { + return EXPOSURE_FRAGMENT_SHADER; + } + + @Override + public void onCreate(int programHandle) { + super.onCreate(programHandle); + exposureLocation = GLES20.glGetUniformLocation(programHandle, "exposure"); + Egloo.checkGlProgramLocation(exposureLocation, "exposure"); + } + + @Override + protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) { + super.onPreDraw(timestampUs, transformMatrix); + GLES20.glUniform1f(exposureLocation, exposure); + Egloo.checkGlError("glUniform1f"); + } + + + @Override + public void onDestroy() { + super.onDestroy(); + exposure = 1f; + exposureLocation = -1; + } + + @Override + public void setParameter1(float value) { + setExposure(value); + } + + @Override + public float getParameter1() { + return getExposure(); + } + + public float getExposure() { + return exposure; + } + /** + * exposure: The adjusted exposure (-10.0 - 10.0, with 0.0 as the default) + */ + public void setExposure(float exposure) { + this.exposure = exposure; + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/HalftoneFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/HalftoneFilter.java new file mode 100644 index 00000000..428ee7bb --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/HalftoneFilter.java @@ -0,0 +1,115 @@ +package com.otaliastudios.cameraview.filters; + +import android.opengl.GLES20; + +import androidx.annotation.NonNull; + +import com.otaliastudios.cameraview.filter.BaseFilter; +import com.otaliastudios.cameraview.filter.OneParameterFilter; +import com.otaliastudios.cameraview.filter.TwoParameterFilter; +import com.otaliastudios.opengl.core.Egloo; + +/** + * exposure: The adjusted exposure (-10.0 - 10.0, with 0.0 as the default) + */ +public class HalftoneFilter extends BaseFilter implements TwoParameterFilter { + private static final String EXPOSURE_FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;" + + "varying vec2 vTextureCoord;\n" + + + "uniform samplerExternalOES sTexture;\n" + + + "uniform highp float fractionalWidthOfPixel;\n" + + "uniform highp float aspectRatio;\n" + + + "const highp vec3 W = vec3(0.2125, 0.7154, 0.0721);\n" + + + "void main()\n" + + "{\n" + + " highp vec2 sampleDivisor = vec2(fractionalWidthOfPixel, fractionalWidthOfPixel / aspectRatio);\n" + + " highp vec2 samplePos = vTextureCoord - mod(vTextureCoord, sampleDivisor) + 0.5 * sampleDivisor;\n" + + " highp vec2 textureCoordinateToUse = vec2(vTextureCoord.x, (vTextureCoord.y * aspectRatio + 0.5 - 0.5 * aspectRatio));\n" + + " highp vec2 adjustedSamplePos = vec2(samplePos.x, (samplePos.y * aspectRatio + 0.5 - 0.5 * aspectRatio));\n" + + " highp float distanceFromSamplePoint = distance(adjustedSamplePos, textureCoordinateToUse);\n" + + " lowp vec3 sampledColor = texture2D(sTexture, samplePos).rgb;\n" + + " highp float dotScaling = 1.0 - dot(sampledColor, W);\n" + + " lowp float checkForPresenceWithinDot = 1.0 - step(distanceFromSamplePoint, (fractionalWidthOfPixel * 0.5) * dotScaling);\n" + + " gl_FragColor = vec4(vec3(checkForPresenceWithinDot), 1.0);\n" + + "}"; + + private float fractionalWidthOfPixel = 0.01f; + private int fractionalWidthOfPixelLocation = -1; + private float aspectRatio = 1f; + private int aspectRatioLocation = -1; + + @NonNull + @Override + public String getFragmentShader() { + return EXPOSURE_FRAGMENT_SHADER; + } + + @Override + public void onCreate(int programHandle) { + super.onCreate(programHandle); + + fractionalWidthOfPixelLocation = GLES20.glGetUniformLocation(programHandle, "fractionalWidthOfPixel"); + Egloo.checkGlProgramLocation(fractionalWidthOfPixelLocation, "fractionalWidthOfPixel"); + + aspectRatioLocation = GLES20.glGetUniformLocation(programHandle, "aspectRatio"); + Egloo.checkGlProgramLocation(aspectRatioLocation, "aspectRatio"); + } + + @Override + protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) { + super.onPreDraw(timestampUs, transformMatrix); + GLES20.glUniform1f(fractionalWidthOfPixelLocation, fractionalWidthOfPixel); + GLES20.glUniform1f(aspectRatioLocation, aspectRatio); + Egloo.checkGlError("glUniform1f"); + } + + + @Override + public void onDestroy() { + super.onDestroy(); + fractionalWidthOfPixel = 0.01f; + fractionalWidthOfPixelLocation = -1; + aspectRatio = 1f; + aspectRatioLocation = -1; + } + + @Override + public void setParameter1(float value) { + setFractionalWidthOfPixel(value); + } + + @Override + public float getParameter1() { + return getFractionalWidthOfPixel(); + } + + @Override + public void setParameter2(float value) { + setAspectRatio(value); + } + + @Override + public float getParameter2() { + return getAspectRatio(); + } + + public float getFractionalWidthOfPixel() { + return fractionalWidthOfPixel; + } + + public void setFractionalWidthOfPixel(float fractionalWidthOfPixel) { + this.fractionalWidthOfPixel = fractionalWidthOfPixel; + } + + public float getAspectRatio() { + return aspectRatio; + } + + public void setAspectRatio(float aspectRatio) { + this.aspectRatio = aspectRatio; + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/HighEdgeGlowFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/HighEdgeGlowFilter.java new file mode 100644 index 00000000..b1006e98 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/HighEdgeGlowFilter.java @@ -0,0 +1,141 @@ +package com.otaliastudios.cameraview.filters; + +import android.opengl.GLES20; + +import androidx.annotation.NonNull; + +import com.otaliastudios.cameraview.filter.BaseFilter; +import com.otaliastudios.cameraview.size.Size; +import com.otaliastudios.opengl.core.Egloo; + +import java.nio.FloatBuffer; + +public class HighEdgeGlowFilter extends BaseFilter { + private static final String FRAGMENT_SHADER = + "#extension GL_OES_EGL_image_external : require\n" + + "precision highp float;\n" + + "\n" + + "uniform vec3 iResolution;\n" + + "uniform float iTime;\n" + + "uniform samplerExternalOES iChannel0;\n" + + "varying vec2 " + DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME + ";\n" + + "\n" + + "/* Returns rgb vec from input 0-1 */\n" + + "vec3 getRainbowColor(in float val) {\n" + + " /*convert to rainbow RGB*/\n" + + " float a = (1.0 - val) * 6.0;\n" + + " int X = int(floor(a));\n" + + " float Y = a - float(X);\n" + + " float r = 0.;\n" + + " float g = 0.;\n" + + " float b = 0.;\n" + + " if (X == 0) {\n" + + " r = 1.; g = Y; b = 0.;\n" + + " } else if (X == 1) {\n" + + " r = 1. - Y; g = 1.; b = 0.;\n" + + " } else if (X == 2) {\n" + + " r = 0.; g = 1.; b = Y;\n" + + " } else if (X == 3) {\n" + + " r = 0.; g = 1. - Y; b = 1.;\n" + + " } else if (X == 4) {\n" + + " r = Y; g = 0.; b = 1.;\n" + + " } else if (X == 5) {\n" + + " r = 1.; g = 0.; b = 1. - Y;\n" + + " } else {\n" + + " r = 0.; g = 0.; b = 0.;\n" + + " }\n" + + " return vec3(r, g, b);\n" + + "}\n" + + "\n" + + "float d;\n" + + "\n" + + "float lookup(vec2 p, float dx, float dy, float edgeIntensity)\n" + + "{\n" + + " vec2 uv = (p.xy + vec2(dx * edgeIntensity, dy * edgeIntensity)) / iResolution.xy;\n" + + " vec4 c = texture2D(iChannel0, uv.xy);\n" + + "\t\n" + + "\t// return as luma\n" + + " return 0.2126*c.r + 0.7152*c.g + 0.0722*c.b;\n" + + "}\n" + + "\n" + + "void mainImage( out vec4 fragColor, in vec2 fragCoord )\n" + + "{\n" + + " float timeNorm = mod(iTime, 5.) / 5.;\n" + + " vec3 glowCol = getRainbowColor(timeNorm);\n" + + " float edgeIntensity = 10.;\n" + + " vec2 p = fragCoord.xy;\n" + + " \n" + + "\t// simple sobel edge detection\n" + + " float gx = 0.0;\n" + + " gx += -1.0 * lookup(p, -1.0, -1.0, edgeIntensity);\n" + + " gx += -2.0 * lookup(p, -1.0, 0.0, edgeIntensity);\n" + + " gx += -1.0 * lookup(p, -1.0, 1.0, edgeIntensity);\n" + + " gx += 1.0 * lookup(p, 1.0, -1.0, edgeIntensity);\n" + + " gx += 2.0 * lookup(p, 1.0, 0.0, edgeIntensity);\n" + + " gx += 1.0 * lookup(p, 1.0, 1.0, edgeIntensity);\n" + + " \n" + + " float gy = 0.0;\n" + + " gy += -1.0 * lookup(p, -1.0, -1.0, edgeIntensity);\n" + + " gy += -2.0 * lookup(p, 0.0, -1.0, edgeIntensity);\n" + + " gy += -1.0 * lookup(p, 1.0, -1.0, edgeIntensity);\n" + + " gy += 1.0 * lookup(p, -1.0, 1.0, edgeIntensity);\n" + + " gy += 2.0 * lookup(p, 0.0, 1.0, edgeIntensity);\n" + + " gy += 1.0 * lookup(p, 1.0, 1.0, edgeIntensity);\n" + + " \n" + + "\t// hack: use g^2 to conceal noise in the video\n" + + " float g = gx*gx + gy*gy;\n" + + " \n" + + " vec4 col = texture2D(iChannel0, p / iResolution.xy);\n" + + " col += vec4(g * glowCol, 1.0);\n" + + " \n" + + " fragColor = col;\n" + + "}" + + "\n" + + "void main() {\n" + + "\tmainImage(gl_FragColor, " + DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME + " * iResolution.xy);\n" + + "}"; + + + long START_TIME = System.currentTimeMillis(); + private int iGlobalTimeLocation = -1; + private int iResolutionLocation = -1; + + @NonNull + @Override + public String getFragmentShader() { + return FRAGMENT_SHADER; + } + + @Override + public void onDestroy() { + super.onDestroy(); + iGlobalTimeLocation = -1; + START_TIME = System.currentTimeMillis(); + } + + @Override + public void onCreate(int programHandle) { + super.onCreate(programHandle); + iResolutionLocation = GLES20.glGetUniformLocation(programHandle, "iResolution"); + Egloo.checkGlProgramLocation(iResolutionLocation, "iResolution"); + + iGlobalTimeLocation = GLES20.glGetUniformLocation(programHandle, "iTime"); + Egloo.checkGlProgramLocation(iGlobalTimeLocation, "iTime"); + } + + @Override + protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) { + super.onPreDraw(timestampUs, transformMatrix); + + Size size = getSize(); + if (size != null) { + GLES20.glUniform3fv(iResolutionLocation, 1, + FloatBuffer.wrap(new float[]{(float) size.getWidth(), (float) size.getHeight(), 1.0f})); + Egloo.checkGlError("glUniform3fv"); + } + + float time = ((float) (System.currentTimeMillis() - START_TIME)) / 1000.0f; + GLES20.glUniform1f(iGlobalTimeLocation, time); + Egloo.checkGlError("glUniform1f"); + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/HighlightShadowFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/HighlightShadowFilter.java new file mode 100644 index 00000000..24d0c69e --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/HighlightShadowFilter.java @@ -0,0 +1,112 @@ +package com.otaliastudios.cameraview.filters; + +import android.opengl.GLES20; + +import androidx.annotation.NonNull; + +import com.otaliastudios.cameraview.filter.BaseFilter; +import com.otaliastudios.cameraview.filter.TwoParameterFilter; +import com.otaliastudios.opengl.core.Egloo; + +/** + * between 0.0 to 1.0 + */ +public class HighlightShadowFilter extends BaseFilter implements TwoParameterFilter { + private static final String EXPOSURE_FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;" + + " uniform samplerExternalOES sTexture;\n" + + " uniform float shadows;\n" + + " uniform float highlights;\n" + + " varying vec2 vTextureCoord;\n" + + " \n" + + " const mediump vec3 luminanceWeighting = vec3(0.3, 0.3, 0.3);\n" + + " \n" + + " void main()\n" + + " {\n" + + " vec4 source = texture2D(sTexture, vTextureCoord);\n" + + " mediump float luminance = dot(source.rgb, luminanceWeighting);\n" + + " \n" + + " mediump float shadow = clamp((pow(luminance, 1.0/(shadows+1.0)) + (-0.76)*pow(luminance, 2.0/(shadows+1.0))) - luminance, 0.0, 1.0);\n" + + " mediump float highlight = clamp((1.0 - (pow(1.0-luminance, 1.0/(2.0-highlights)) + (-0.8)*pow(1.0-luminance, 2.0/(2.0-highlights)))) - luminance, -1.0, 0.0);\n" + + " lowp vec3 result = vec3(0.0, 0.0, 0.0) + ((luminance + shadow + highlight) - 0.0) * ((source.rgb - vec3(0.0, 0.0, 0.0))/(luminance - 0.0));\n" + + " \n" + + " gl_FragColor = vec4(result.rgb, source.a);\n" + + " }"; + + private float shadows = 1f; + private int shadowsLocation = -1; + + private float highlights = 0f; + private int highlightsLocation = -1; + + @NonNull + @Override + public String getFragmentShader() { + return EXPOSURE_FRAGMENT_SHADER; + } + + @Override + public void onCreate(int programHandle) { + super.onCreate(programHandle); + + shadowsLocation = GLES20.glGetUniformLocation(programHandle, "shadows"); + Egloo.checkGlProgramLocation(shadowsLocation, "shadows"); + + highlightsLocation = GLES20.glGetUniformLocation(programHandle, "highlights"); + Egloo.checkGlProgramLocation(highlightsLocation, "highlights"); + } + + @Override + protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) { + super.onPreDraw(timestampUs, transformMatrix); + GLES20.glUniform1f(shadowsLocation, shadows); + GLES20.glUniform1f(highlightsLocation, highlights); + Egloo.checkGlError("glUniform1f"); + } + + + @Override + public void onDestroy() { + super.onDestroy(); + shadows = 1f; + shadowsLocation = -1; + highlights = 0f; + highlightsLocation = -1; + } + + @Override + public void setParameter1(float value) { + setShadows(value); + } + + @Override + public float getParameter1() { + return getShadows(); + } + + @Override + public void setParameter2(float value) { + setHighlights(value); + } + + @Override + public float getParameter2() { + return getHighlights(); + } + + public float getShadows() { + return shadows; + } + + public void setShadows(float shadows) { + this.shadows = shadows; + } + + public float getHighlights() { + return highlights; + } + + public void setHighlights(float highlights) { + this.highlights = highlights; + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/LuminanceThresholdFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/LuminanceThresholdFilter.java new file mode 100644 index 00000000..a10c123a --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/LuminanceThresholdFilter.java @@ -0,0 +1,82 @@ +package com.otaliastudios.cameraview.filters; + +import android.opengl.GLES20; + +import androidx.annotation.NonNull; + +import com.otaliastudios.cameraview.filter.BaseFilter; +import com.otaliastudios.cameraview.filter.OneParameterFilter; +import com.otaliastudios.opengl.core.Egloo; + +/** + * exposure: The adjusted exposure (-10.0 - 10.0, with 0.0 as the default) + */ +public class LuminanceThresholdFilter extends BaseFilter implements OneParameterFilter { + private static final String EXPOSURE_FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;" + + "varying highp vec2 vTextureCoord;\n" + + "\n" + + "uniform samplerExternalOES sTexture;\n" + + "uniform highp float threshold;\n" + + "\n" + + "const highp vec3 W = vec3(0.2125, 0.7154, 0.0721);\n" + + "\n" + + "void main()\n" + + "{\n" + + " highp vec4 textureColor = texture2D(sTexture, vTextureCoord);\n" + + " highp float luminance = dot(textureColor.rgb, W);\n" + + " highp float thresholdResult = step(threshold, luminance);\n" + + " \n" + + " gl_FragColor = vec4(vec3(thresholdResult), textureColor.w);\n" + + "}"; + + + private float threshold = 0.5f; + private int thresholdLocation = -1; + + @NonNull + @Override + public String getFragmentShader() { + return EXPOSURE_FRAGMENT_SHADER; + } + + @Override + public void onCreate(int programHandle) { + super.onCreate(programHandle); + thresholdLocation = GLES20.glGetUniformLocation(programHandle, "threshold"); + Egloo.checkGlProgramLocation(thresholdLocation, "threshold"); + } + + @Override + protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) { + super.onPreDraw(timestampUs, transformMatrix); + GLES20.glUniform1f(thresholdLocation, threshold); + Egloo.checkGlError("glUniform1f"); + } + + + @Override + public void onDestroy() { + super.onDestroy(); + threshold = 0.5f; + thresholdLocation = -1; + } + + @Override + public void setParameter1(float value) { + setThreshold(value); + } + + @Override + public float getParameter1() { + return getThreshold(); + } + + public float getThreshold() { + return threshold; + } + + public void setThreshold(float threshold) { + this.threshold = threshold; + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/MirrorFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/MirrorFilter.java new file mode 100644 index 00000000..57f6427e --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/MirrorFilter.java @@ -0,0 +1,49 @@ +package com.otaliastudios.cameraview.filters; + +import androidx.annotation.NonNull; + +import com.otaliastudios.cameraview.filter.BaseFilter; + +public class MirrorFilter extends BaseFilter { + private static final String FRAGMENT_SHADER = + "#extension GL_OES_EGL_image_external : require\n" + + "precision highp float;\n" + + "\n" + + "uniform samplerExternalOES iChannel0;\n" + + "varying vec2 " + DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME + ";\n" + + "\n" + + "void mainImage( out vec4 fragColor, in vec2 fragCoord )\n" + + " {\n" + + " vec2 flipCoord = vec2(1.0-fragCoord.x, fragCoord.y);\n" + + " if(flipCoord.x >= 0.5){\n" + + " \tfragColor = texture2D(iChannel0, vec2( flipCoord.x - 0.5, flipCoord.y ));\n" + + " } else {\n" + + " \tfragColor = texture2D(iChannel0, vec2( 0.5 - flipCoord.x,flipCoord.y ));\n" + + " }\n" + + " }\n" + + "\n" + + "void main() {\n" + + " \tmainImage(gl_FragColor, " + DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME + ");\n" + + " }"; + + @NonNull + @Override + public String getFragmentShader() { + return FRAGMENT_SHADER; + } + + @Override + public void onDestroy() { + super.onDestroy(); + } + + @Override + public void onCreate(int programHandle) { + super.onCreate(programHandle); + } + + @Override + protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) { + super.onPreDraw(timestampUs, transformMatrix); + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/NeonEdgeGlowFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/NeonEdgeGlowFilter.java new file mode 100644 index 00000000..9da4a35e --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/NeonEdgeGlowFilter.java @@ -0,0 +1,143 @@ +package com.otaliastudios.cameraview.filters; + +import android.opengl.GLES20; + +import androidx.annotation.NonNull; + +import com.otaliastudios.cameraview.filter.BaseFilter; +import com.otaliastudios.cameraview.size.Size; +import com.otaliastudios.opengl.core.Egloo; + +import java.nio.FloatBuffer; + +public class NeonEdgeGlowFilter extends BaseFilter { + private static final String FRAGMENT_SHADER = + "#extension GL_OES_EGL_image_external : require\n" + + "precision highp float;\n" + + "\n" + + "uniform vec3 iResolution;\n" + + "uniform float iTime;\n" + + "uniform samplerExternalOES iChannel0;\n" + + "varying vec2 " + DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME + ";\n" + + "\n" + + "/* Returns rgb vec from input 0-1 */\n" + + "vec3 getRainbowColor(in float val) {\n" + + " /*convert to rainbow RGB*/\n" + + " float a = (1.0 - val) * 6.0;\n" + + " int X = int(floor(a));\n" + + " float Y = a - float(X);\n" + + " float r = 0.;\n" + + " float g = 0.;\n" + + " float b = 0.;\n" + + " if (X == 0) {\n" + + " r = 1.; g = Y; b = 0.;\n" + + " } else if (X == 1) {\n" + + " r = 1. - Y; g = 1.; b = 0.;\n" + + " } else if (X == 2) {\n" + + " r = 0.; g = 1.; b = Y;\n" + + " } else if (X == 3) {\n" + + " r = 0.; g = 1. - Y; b = 1.;\n" + + " } else if (X == 4) {\n" + + " r = Y; g = 0.; b = 1.;\n" + + " } else if (X == 5) {\n" + + " r = 1.; g = 0.; b = 1. - Y;\n" + + " } else {\n" + + " r = 0.; g = 0.; b = 0.;\n" + + " }\n" + + " return vec3(57,255,20);\n" + + "}\n" + + "\n" + + "float d;\n" + + "\n" + + "float lookup(vec2 p, float dx, float dy, float edgeIntensity)\n" + + "{\n" + + " vec2 uv = (p.xy + vec2(dx * edgeIntensity, dy * edgeIntensity)) / iResolution.xy;\n" + + " vec4 c = texture2D(iChannel0, uv.xy);\n" + + "\t\n" + + "\t// return as luma\n" + + " return 0.2126*c.r + 0.7152*c.g + 0.0722*c.b;\n" + + "}\n" + + "\n" + + "void mainImage( out vec4 fragColor, in vec2 fragCoord )\n" + + "{\n" + + " float timeNorm = mod(iTime, 5.) / 5.;\n" + + " vec3 glowCol = getRainbowColor(timeNorm);\n" + + " float edgeIntensity = 1.;\n" + + " if (timeNorm < .5) { edgeIntensity += (4. * timeNorm);}\n" + + " else { edgeIntensity += -4. * (timeNorm - 1.); }\n" + + " vec2 p = fragCoord.xy;\n" + + " \n" + + "\t// simple sobel edge detection\n" + + " float gx = 0.0;\n" + + " gx += -1.0 * lookup(p, -1.0, -1.0, edgeIntensity);\n" + + " gx += -2.0 * lookup(p, -1.0, 0.0, edgeIntensity);\n" + + " gx += -1.0 * lookup(p, -1.0, 1.0, edgeIntensity);\n" + + " gx += 1.0 * lookup(p, 1.0, -1.0, edgeIntensity);\n" + + " gx += 2.0 * lookup(p, 1.0, 0.0, edgeIntensity);\n" + + " gx += 1.0 * lookup(p, 1.0, 1.0, edgeIntensity);\n" + + " \n" + + " float gy = 0.0;\n" + + " gy += -1.0 * lookup(p, -1.0, -1.0, edgeIntensity);\n" + + " gy += -2.0 * lookup(p, 0.0, -1.0, edgeIntensity);\n" + + " gy += -1.0 * lookup(p, 1.0, -1.0, edgeIntensity);\n" + + " gy += 1.0 * lookup(p, -1.0, 1.0, edgeIntensity);\n" + + " gy += 2.0 * lookup(p, 0.0, 1.0, edgeIntensity);\n" + + " gy += 1.0 * lookup(p, 1.0, 1.0, edgeIntensity);\n" + + " \n" + + "\t// hack: use g^2 to conceal noise in the video\n" + + " float g = gx*gx + gy*gy;\n" + + " \n" + + " vec4 col = texture2D(iChannel0, p / iResolution.xy);\n" + + " col += vec4(g * glowCol, 1.0);\n" + + " \n" + + " fragColor = col;\n" + + "}" + + "\n" + + "void main() {\n" + + "\tmainImage(gl_FragColor, " + DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME + " * iResolution.xy);\n" + + "}"; + + + long START_TIME = System.currentTimeMillis(); + private int iGlobalTimeLocation = -1; + private int iResolutionLocation = -1; + + @NonNull + @Override + public String getFragmentShader() { + return FRAGMENT_SHADER; + } + + @Override + public void onDestroy() { + super.onDestroy(); + iGlobalTimeLocation = -1; + START_TIME = System.currentTimeMillis(); + } + + @Override + public void onCreate(int programHandle) { + super.onCreate(programHandle); + iResolutionLocation = GLES20.glGetUniformLocation(programHandle, "iResolution"); + Egloo.checkGlProgramLocation(iResolutionLocation, "iResolution"); + + iGlobalTimeLocation = GLES20.glGetUniformLocation(programHandle, "iTime"); + Egloo.checkGlProgramLocation(iGlobalTimeLocation, "iTime"); + } + + @Override + protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) { + super.onPreDraw(timestampUs, transformMatrix); + + Size size = getSize(); + if (size != null) { + GLES20.glUniform3fv(iResolutionLocation, 1, + FloatBuffer.wrap(new float[]{(float) size.getWidth(), (float) size.getHeight(), 1.0f})); + Egloo.checkGlError("glUniform3fv"); + } + + float time = (((float) (System.currentTimeMillis() - START_TIME)) / 1000.0f) + 1; + GLES20.glUniform1f(iGlobalTimeLocation, time); + Egloo.checkGlError("glUniform1f"); + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/RainbowFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/RainbowFilter.java new file mode 100644 index 00000000..638337b1 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/RainbowFilter.java @@ -0,0 +1,88 @@ +package com.otaliastudios.cameraview.filters; + +import android.opengl.GLES20; + +import androidx.annotation.NonNull; + +import com.otaliastudios.cameraview.filter.BaseFilter; +import com.otaliastudios.cameraview.size.Size; +import com.otaliastudios.opengl.core.Egloo; + +import java.nio.FloatBuffer; + +public class RainbowFilter extends BaseFilter { + private static final String FRAGMENT_SHADER = + "#extension GL_OES_EGL_image_external : require\n" + + "precision highp float;\n" + + "\n" + + "uniform vec3 iResolution;\n" + + "uniform samplerExternalOES iChannel0;\n" + + "uniform float iTime;\n" + + "varying vec2 " + DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME + ";\n" + + "\n" + + "vec2 size = vec2(50.0, 50.0);\n" + + "vec2 distortion = vec2(20.0, 20.0);\n" + + "float speed = 0.75;" + + "\n" + + "void mainImage( out vec4 fragColor, in vec2 fragCoord )\n" + + "{\n" + + " vec2 transformed = vec2(\n" + + " fragCoord.x + sin(fragCoord.y / size.x + iTime * speed) * distortion.x,\n" + + " fragCoord.y + cos(fragCoord.x / size.y + iTime * speed) * distortion.y\n" + + " );\n" + + " vec2 relCoord = fragCoord.xy / iResolution.xy;\n" + + " fragColor = texture2D(iChannel0, fragCoord/iResolution.xy) + vec4(\n" + + " (cos(relCoord.x + iTime * speed * 4.0) + 1.0) / 2.0,\n" + + " (relCoord.x + relCoord.y) / 2.0,\n" + + " (sin(relCoord.y + iTime * speed) + 1.0) / 2.0,\n" + + " 0\n" + + " );\n" + + "}\n" + + "void main() {\n" + + " mainImage(gl_FragColor, " + DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME + ");\n" + + "}"; + + + long START_TIME = System.currentTimeMillis(); + private int iGlobalTimeLocation = -1; + private int iResolutionLocation = -1; + + @NonNull + @Override + public String getFragmentShader() { + return FRAGMENT_SHADER; + } + + @Override + public void onDestroy() { + super.onDestroy(); + iGlobalTimeLocation = -1; + START_TIME = System.currentTimeMillis(); + } + + @Override + public void onCreate(int programHandle) { + super.onCreate(programHandle); + iResolutionLocation = GLES20.glGetUniformLocation(programHandle, "iResolution"); + Egloo.checkGlProgramLocation(iResolutionLocation, "iResolution"); + + iGlobalTimeLocation = GLES20.glGetUniformLocation(programHandle, "iTime"); + Egloo.checkGlProgramLocation(iGlobalTimeLocation, "iTime"); + } + + @Override + protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) { + super.onPreDraw(timestampUs, transformMatrix); + + Size size = getSize(); + if (size != null) { + GLES20.glUniform3fv(iResolutionLocation, 1, + FloatBuffer.wrap(new float[]{(float) size.getWidth(), (float) size.getHeight(), 1.0f})); + Egloo.checkGlError("glUniform3fv"); + } + + float time = ((float) (System.currentTimeMillis() - START_TIME)) / 1000.0f; + GLES20.glUniform1f(iGlobalTimeLocation, time); + Egloo.checkGlError("glUniform1f"); + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/TextureHelper.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/TextureHelper.java new file mode 100644 index 00000000..83f5c0fe --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/TextureHelper.java @@ -0,0 +1,46 @@ +package com.otaliastudios.cameraview.filters; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.opengl.GLES20; +import android.opengl.GLUtils; + +public class TextureHelper { + public static int loadTexture(final Context context, final int resourceId) + { + final int[] textureHandle = new int[1]; + + GLES20.glGenTextures(1, textureHandle, 0); + + if (textureHandle[0] != 0) + { + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inScaled = false; // No pre-scaling + + // Read in the resource + final Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resourceId, options); + + // Bind to the texture in OpenGL + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle[0]); + + // Set filtering + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST); + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST); + + // Load the bitmap into the bound texture. + GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0); + + // Recycle the bitmap, since its data has been loaded into OpenGL. + bitmap.recycle(); + } + + if (textureHandle[0] == 0) + { + throw new RuntimeException("Error loading texture."); + } + + return textureHandle[0]; + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/ToasterFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/ToasterFilter.java new file mode 100644 index 00000000..19797d3a --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/ToasterFilter.java @@ -0,0 +1,90 @@ +package com.otaliastudios.cameraview.filters; + +import android.opengl.GLES20; + +import androidx.annotation.NonNull; + +import com.otaliastudios.cameraview.filter.BaseFilter; +import com.otaliastudios.cameraview.size.Size; +import com.otaliastudios.opengl.core.Egloo; + +import java.nio.FloatBuffer; + +public class ToasterFilter extends BaseFilter { + private static final String FRAGMENT_SHADER = + "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float; \n" + + "uniform samplerExternalOES u_Texture0; \n" + +// "uniform sampler2D u_Texture4;\n" + + "varying vec2 " + DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME + "; \n" + + "const vec3 W = vec3(0.2125, 0.7154, 0.0721);\n" + + "\n" + + "vec3 BrightnessContrastSaturation(vec3 color, float brt, float con, float sat)\n" + + "{\n" + + "\tvec3 black = vec3(0., 0., 0.);\n" + + "\tvec3 middle = vec3(0.5, 0.5, 0.5);\n" + + "\tfloat luminance = dot(color, W);\n" + + "\tvec3 gray = vec3(luminance, luminance, luminance);\n" + + "\t\n" + + "\tvec3 brtColor = mix(black, color, brt);\n" + + "\tvec3 conColor = mix(middle, brtColor, con);\n" + + "\tvec3 satColor = mix(gray, conColor, sat);\n" + + "\treturn satColor;\n" + + "}\n" + + "\n" + + "vec3 ovelayBlender(vec3 Color, vec3 filter){\n" + + "\tvec3 filter_result;\n" + + "\t\n" + + "\t//if(luminance < 0.5)\n" + + "\t//\tfilter_result = 2. * trans_filter * Color;\n" + + "\t//else\n" + + "\t\tfilter_result = 1. - (1. - (2. *(filter - 0.5)))*(1. - Color);\n" + + "\t\t\n" + + "\treturn filter_result;\n" + + "}\n" + + "\n" + + "void main()\n" + + "{\n" + + "\t //get the pixel\n" + + " vec2 st = " + DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME + ".st;\n" + + " vec3 irgb = texture2D(u_Texture0, st).rgb;\n" + +// " vec3 filter = texture2D(u_Texture4, st).rgb;\n" + + " \n" + + " //adjust the brightness/contrast/saturation\n" + + " float T_bright = 1.0;\n" + + " float T_contrast = 1.0;\n" + + " float T_saturation = 1.0;\n" + + " vec3 bcs_result = BrightnessContrastSaturation(irgb, T_bright, T_contrast, T_saturation);\n" + + " \n" + + " //more red\n" + + " vec3 rb_result = vec3(bcs_result.r*1.3, bcs_result.g, bcs_result.b*0.9);\n" + + " \n" + +// " //add filter (overlay blending)\n" + +// " vec3 after_filter = mix(bcs_result, ovelayBlender(bcs_result, filter), 0.55);\n" + + " \t\n" + + " gl_FragColor = vec4(rb_result, 1.);\n" + + "}"; + + + @NonNull + @Override + public String getFragmentShader() { + return FRAGMENT_SHADER; + } + + @Override + public void onDestroy() { + super.onDestroy(); + } + + @Override + public void onCreate(int programHandle) { + super.onCreate(programHandle); + } + + @Override + protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) { + super.onPreDraw(timestampUs, transformMatrix); + } + +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/WavesReflectionFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/WavesReflectionFilter.java new file mode 100644 index 00000000..1b16596a --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/WavesReflectionFilter.java @@ -0,0 +1,38 @@ +package com.otaliastudios.cameraview.filters; + +import androidx.annotation.NonNull; + +import com.otaliastudios.cameraview.filter.BaseFilter; + +public class WavesReflectionFilter extends BaseFilter { + private static final String FRAGMENT_SHADER = + "#extension GL_OES_EGL_image_external : require\n" + + "precision highp float;\n" + + "\n" + + "uniform vec3 iResolution;\n" + + "uniform samplerExternalOES iChannel0;\n" + + "varying vec2 "+DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME+";\n" + + "\n" + + "float waterLevel = 0.5;\n" + + "float waveAmplitude = 0.01;\n" + + "\n" + + "void mainImage( out vec4 fragColor, in vec2 fragCoord )\n" + + " {\n" + + " if(fragCoord.y >= waterLevel){\n" + + " fragColor = texture2D(iChannel0, fragCoord);\n" + + " }else{\n" + + " fragColor = texture2D(iChannel0,vec2(fragCoord.x + fract(sin(dot(fragCoord.xy ,vec2(12.9898,78.233))) * 43758.5453) * waveAmplitude,\n" + + " \t\t\t\t2.0 * waterLevel - fragCoord.y));\n" + + " }\n" + + " }\n" + + "\n" + + "void main() {\n" + + " \tmainImage(gl_FragColor, "+DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME+");\n" + + " }"; + + @NonNull + @Override + public String getFragmentShader() { + return FRAGMENT_SHADER; + } +} diff --git a/cameraview/src/main/res/drawable/filter2.png b/cameraview/src/main/res/drawable/filter2.png new file mode 100644 index 00000000..ac50ef34 Binary files /dev/null and b/cameraview/src/main/res/drawable/filter2.png differ