I want to implement the Hue/color/saturation color overlays.
I saw the macros:
#define ColorBlend_Saturation(T,A,B) ColorBlend_Hls(T,A,B,HueA,LuminationA,SaturationB)
I am trying to reproduce it in Adobe Photoshop with colors #332244 and #557711 to get the result color - #431076. However, after applying these macros I get color - #320C59 as the result.
question 1: how can I reproduce the photoshop algorithms for hue, saturation and color?
question 2: how can I adjust the alpha channel? for example, on my colors and optically == 50, this should be in photoshop - #3b195d
question 1:
Photoshop’s hue, saturation, color, and luminosity blend modes are based on a color space with dimensions that the article HSL and HSV calls hue, chroma, and luma. Note that this space is different from both HSL and HSV, and only the hue dimension is shared between the three; see that article for details.
The Hue blend mode preserves the luma and chroma of the bottom layer, while adopting the hue of the top layer.
The Saturation blend mode preserves the luma and hue of the bottom layer, while adopting the chroma of the top layer.
The Color blend mode preserves the luma of the bottom layer, while adopting the hue and chroma of the top layer.
from http://en.wikipedia.org/wiki/Blend_modes
After more than 3 hours of experimenting i succeded to upgrade HSV -> RGB converter to working saturation blender. Other Blending modes should be simmilar.
Here is code:
#include <cmath>
#include <iostream>
using namespace std;
struct HSVColor
{
float H,S,V;
};
struct RGBColor
{
float R,G,B;
RGBColor() = default;
RGBColor(int r,int g, int b):
R(r/255.0),
G(g/255.0),
B(b/255.0)
{
}
};
HSVColor RGBToHSV(const RGBColor& RGB)
{
float Max;
float Min;
float Chroma;
HSVColor HSV;
Min = min(min(RGB.R, RGB.G), RGB.B);
Max = max(max(RGB.R, RGB.G), RGB.B);
Chroma = Max - Min;
//If Chroma is 0, then S is 0 by definition, and H is undefined but 0 by convention.
if(Chroma != 0)
{
if(RGB.R == Max)
{
HSV.H = (RGB.G - RGB.B) / Chroma;
if(HSV.H < 0.0)
{
HSV.H += 6.0;
}
}
else if(RGB.G == Max)
{
HSV.H = ((RGB.B - RGB.R) / Chroma) + 2.0;
}
else //RGB.B == Max
{
HSV.H = ((RGB.R - RGB.G) / Chroma) + 4.0;
}
HSV.H *= 60.0;
HSV.S = Chroma / Max;
}
HSV.V = Max;
return HSV;
}
RGBColor Saturate(const HSVColor& HSV,const HSVColor& overlay)
{
float os = overlay.S;
float ov = overlay.V;
float Min;
float Chroma;
float Hdash;
float X;
RGBColor RGB{0,0,0};
Chroma = os * ov; // Orginal was HSV.S * HSV.V
Hdash = HSV.H / 60.0;
X = Chroma * (1.0 - abs(fmod(Hdash , 2.0) - 1.0));
if(Hdash < 1.0)
{
RGB.R = Chroma;
RGB.G = X;
}
else if(Hdash < 2.0)
{
RGB.R = X;
RGB.G = Chroma;
}
else if(Hdash < 3.0)
{
RGB.G = Chroma;
RGB.B = X;
}
else if(Hdash < 4.0)
{
RGB.G= X;
RGB.B = Chroma;
}
else if(Hdash < 5.0)
{
RGB.R = X;
RGB.B = Chroma;
}
else if(Hdash <= 6.0)
{
RGB.R = Chroma;
RGB.B = X;
}
Min = ov - Chroma; // Orginal was HSV.V - Chroma
RGB.R += Min;
RGB.G += Min;
RGB.B += Min;
return RGB;
}
int main(){
RGBColor base{51, 34, 68};
RGBColor overly{85, 119, 17};
RGBColor r = Saturate(RGBToHSV(base),RGBToHSV(overly));
cout << int(r.R*255) << endl;
cout << int(r.G*255) << endl;
cout << int(r.B*255) << endl;
}
original HSV <-> RGB converter code here: http://wiki.beyondunreal.com/HSV-RGB_Conversion
question 2.
With Saturation this is actually easy, After saturation blending use normal alpha blending in rgb color space.
RGBColor base;
RGBColor overly;
RGBColor saturated = Saturate(base,overly);
RGBColor result = AlphaBlend(base,saturated,overly.alpha);
Note: this may not work with other blending modes.
Here are equations for different Photoshop blending modes:
inline float Blend_Normal( float Base, float Overlay )
{
return Base;
}
inline float Blend_Lighten( float Base, float Overlay )
{
return ( Overlay > Base ) ? Overlay : Base;
}
inline float Blend_Darken( float Base, float Overlay )
{
return ( Overlay > Base ) ? Base : Overlay;
}
inline float Blend_Multiply( float Base, float Overlay )
{
return Base * Overlay;
}
inline float Blend_Average( float Base, float Overlay )
{
return ( Base + Overlay ) / 2.0f;
}
inline float Blend_Add( float Base, float Overlay )
{
return LMin( Base + Overlay, 1.0f );
}
inline float Blend_Subtract( float Base, float Overlay )
{
return LMax( Base + Overlay - 1.0f, 0.0f );
}
inline float Blend_Difference( float Base, float Overlay )
{
return fabs( Base - Overlay );
}
inline float Blend_Negation( float Base, float Overlay )
{
return 1.0f - fabs( 1.0f - Base - Overlay );
}
inline float Blend_Screen( float Base, float Overlay )
{
return 1.0f - ( 1.0f - Base ) * ( 1.0f - Overlay );
}
inline float Blend_Exclusion( float Base, float Overlay )
{
return Base + Overlay - 2 * Base * Overlay;
}
inline float Blend_Overlay( float Base, float Overlay )
{
return ( Overlay < 0.5f ) ? ( 2.0f * Base * Overlay ) : ( 2.0f * Base - 1.0f ) * ( 1.0f - Overlay );
}
inline float Blend_SoftLight( float Base, float Overlay )
{
return ( Overlay < 0.5f ) ? ( Base + 0.5f ) * Overlay : ( Base - 0.5f ) * ( 1.0f - Overlay );
}
inline float Blend_HardLight( float Base, float Overlay )
{
return Blend_Overlay( Overlay, Base );
}
inline float Blend_ColorDodge( float Base, float Overlay )
{
return ( Overlay > 1.0f - Math::EPSILON ) ? Overlay : LMin( 1.0f, Base / ( 1.0f - Overlay ) );
}
inline float Blend_ColorBurn( float Base, float Overlay )
{
return ( Overlay < Math::EPSILON ) ? Overlay : LMax( 0.0f, 1.0f - ( 1.0f - Base ) / Overlay );
}
inline float Blend_LinearDodge( float Base, float Overlay )
{
return Blend_Add( Base, Overlay );
}
inline float Blend_LinearBurn( float Base, float Overlay )
{
return Blend_Subtract( Base, Overlay );
}
inline float Blend_LinearLight( float Base, float Overlay )
{
return ( Overlay < 0.5f ) ? Blend_LinearBurn( Base, 2 * Overlay ) : Blend_LinearDodge( Base, ( 2 * ( Overlay - 0.5f ) ) );
}
inline float Blend_VividLight( float Base, float Overlay )
{
return ( Overlay < 0.5f ) ? Blend_ColorBurn( Base, 2 * Overlay ) : Blend_ColorDodge( Base, ( 2 * ( Overlay - 0.5f ) ) );
}
inline float Blend_PinLight( float Base, float Overlay )
{
return ( Overlay < 0.5f ) ? Blend_Darken( Base, 2 * Overlay ) : Blend_Lighten( Base, ( 2 * ( Overlay - 0.5f ) ) );
}
inline float Blend_HardMix( float Base, float Overlay )
{
return ( Blend_VividLight( Base, Overlay ) < 0.5f ) ? 0.0f : 1.0f;
}
inline float Blend_Reflect( float Base, float Overlay )
{
return ( Overlay > 1.0f - Math::EPSILON ) ? Overlay : LMin( 1.0f, Base * Base / ( 1.0f - Overlay ) );
}
inline float Blend_Glow( float Base, float Overlay )
{
return Blend_Reflect( Overlay, Base );
}
inline float Blend_Phoenix( float Base, float Overlay )
{
return LMin( Base, Overlay ) - LMax( Base, Overlay ) + 1.0f;
}
From Linderdaum Engine SDK.
Related
There are dozens of image filters written for the Android version of our app in GLSL (ES). As of iOS 12, OpenGL is deprecated, and CIFilter kernels have to be written in Metal.
I had some previous background in OpenGL, however writing CIFilter kernels in Metal is new to me.
Here is one of the filters. Could you help me in translating it to Metal as a CIFilter kernel? That would provide a good example for me so I could translate others.
#extension GL_OES_EGL_image_external : require
precision mediump float;
varying vec2 vTextureCoord;
uniform samplerExternalOES sTexture;
uniform float texelWidth;
uniform float texelHeight;
uniform float intensivity;
void main() {
float SIZE = 1.25 + (intensivity / 100.0)*2.0;
vec4 color;
float min = 1.0;
float max = 0.0;
float val = 0.0;
for (float x = -SIZE; x < SIZE; x++) {
for (float y = -SIZE; y < SIZE; y++) {
color = texture2D(sTexture, vTextureCoord + vec2(x * texelWidth, y * texelHeight));
val = (color.r + color.g + color.b) / 3.;
if (val > max) { max = val; } else if (val < min) { min = val; }
}
}
float range = 5. * (max - min);
gl_FragColor = vec4(pow(1. - range, SIZE * 1.5));
gl_FragColor = vec4((gl_FragColor.r + gl_FragColor.g + gl_FragColor.b) / 3. > 0.75 ? vec3(1.) : gl_FragColor.rgb, 1.);
}
Here's the Metal source for a kernel that attempts to replicate your described filter:
#include <metal_stdlib>
#include <CoreImage/CoreImage.h>
using namespace metal;
extern "C" {
namespace coreimage {
float4 sketch(sampler src, float texelWidth, float texelHeight, float intensity40) {
float size = 1.25f + (intensity40 / 100.0f) * 2.0f;
float minVal = 1.0f;
float maxVal = 0.0f;
for (float x = -size; x < size; ++x) {
for (float y = -size; y < size; ++y) {
float4 color = src.sample(src.coord() + float2(x * texelWidth, y * texelHeight));
float val = (color.r + color.g + color.b) / 3.0f;
if (val > maxVal) {
maxVal = val;
} else if (val < minVal) {
minVal = val;
}
}
}
float range = 5.0f * (maxVal - minVal);
float4 outColor(pow(1.0f - range, size * 1.5f));
outColor = float4((outColor.r + outColor.g + outColor.b) / 3.0f > 0.75f ? float3(1.0f) : outColor.rgb, 1.0f);
return outColor;
}
}
}
I assume you're already familiar with the basics of how to correctly build Metal shaders into a library that can be loaded by Core Image.
You can instantiate your kernel at runtime by loading the default Metal library and requesting the "sketch" function (the name is arbitrary, so long as it matches the kernel source):
NSURL *libraryURL = [NSBundle.mainBundle URLForResource:#"default" withExtension:#"metallib"];
NSData *libraryData = [NSData dataWithContentsOfURL:libraryURL];
NSError *error;
CIKernel *kernel = [CIKernel kernelWithFunctionName:#"sketch" fromMetalLibraryData:libraryData error:&error];
You can then apply this kernel to an image by wrapping it in your own CIFilter subclass, or just invoke it directly:
CIImage *outputImage = [kernel applyWithExtent:CGRectMake(0, 0, width, height)
roiCallback:^CGRect(int index, CGRect destRect)
{ return destRect; }
arguments:#[inputImage, #(1.0f/width), #(1.0f/height), #(60.0f)]];
I've tried to select sensible defaults for each of the arguments (the first of which should be an instance of CIImage), but of course these can be adjusted to taste.
I'm making an app to control the LED color of 5050 LED strip. The color picker provides me with RGB code, but the 5050 LED strip follows CMYK color format. Is there any way to convert RGB values (0-255) to CMYK value (0-99) in android studio?
Here is a simple class that will convert RGB to CMYK, normal cmyk is from 0.0 to 1.0, just use a scaler to get from 0 to 99. There is no android color converter that I have found native to convert to cmyk.
public class CMYK
{
float cyan = 0.0f;
float magenta = 0.0f;
float yellow = 0.0f;
float black = 0.0f;
public void convertRGBtoCMYK(int r, int g, int b)
{
float _r = (float) (r / 255);
float _g = (float) (g / 255);
float _b = (float) (b / 255);
black = 1.0f - max(_r, _g, _b);
cyan = (1.0f - _r - black) / (1.0f - black);
magenta = (1.0f - _g - black) / (1.0f - black);
yellow = (1.0f - _b - black) / (1.0f - black);
}
private float max(float a, float b, float c)
{
if (a > b && a > c)
return a;
if (b > a && b > c)
return b;
if (c > a && c > b)
return c;
// all equal just return a
return a;
}
}
I'm trying to figure out how to draw a centered circle using fragment shader. I don't quite understand how to accomplish this. This is what I got so far, but the result is a white screen.
I want to be able to draw it any size and be able to change the offsets as I like (move the circle around).
void main()
{
float radius = 10.0;
float viewWidth = 340.0;
float viewHeight = 500.0;
float offsetX = viewWidth / 2.0;
float offsetY = viewHeight / 2.0;
float factorX = viewWidth / ( 360.0 / 6.3 );
float factorY = viewHeight / ( 360.0 / 6.3 );
float angleX = gl_FragCoord.x / factorX;
float angleY = gl_FragCoord.y / factorY;
float x = offsetX + ( sin( angleX ) * radius );
float y = offsetY + ( cos( angleY ) * radius );
float c = x + y;
gl_FragColor = vec4( c, c, c, 1.0 );
}
Remember, this program runs separately for each individual fragment. Each one need only decide if it's in or out of the circle. No need to use sin and cos here, just measure the distance from the center of the viewport, to see if the fragment is in the circle.
Here's a disc, which is even simpler: http://glslsandbox.com/e#28997.0
uniform vec2 resolution;
void main( void ) {
vec2 position = ( gl_FragCoord.xy / resolution.xy ) - 0.5;
position.x *= resolution.x / resolution.y;
float circle = 1.0 - smoothstep(0.2, 0.21, length(position));
gl_FragColor = vec4( vec3( circle ), 1.0 );
}
And here's a circle, made by tweaking the disc a little: http://glslsandbox.com/e#28997.1
uniform vec2 resolution;
void main( void ) {
vec2 position = ( gl_FragCoord.xy / resolution.xy ) - 0.5;
position.x *= resolution.x / resolution.y;
float circle = 1.0 - smoothstep(0.003, 0.005, abs(length(position) - 0.2));
gl_FragColor = vec4( vec3( circle ), 1.0 );
}
I am able to draw custom back ground to xml file using Shape
But how to add arc or curv at specified place.
In my library Facebook Like Button I implemented custom Path in order to achieve this goal.
There is ability to specify location of marker wherever you want:
Code from source:
import android.graphics.Path;
import android.graphics.RectF;
public class CalloutPath extends Path {
public static final int MARKER_NONE = 0x0;
public static final int MARKER_LEFT = 0x1;
public static final int MARKER_TOP = 0x2;
public static final int MARKER_RIGHT = 0x4;
public static final int MARKER_BOTTOM = 0x8;
public static final int MARKER_ALL = 0xf;
private final RectF oval = new RectF();
/**
* #param m marker
* #param w width
* #param h height
* #param s stroke thickness
* #param r corners radius
*/
public void build(int m, float w, float h, float s, float r) {
int fl = factor(m, MARKER_LEFT);
int ft = factor(m, MARKER_TOP);
int fr = factor(m, MARKER_RIGHT);
int fb = factor(m, MARKER_BOTTOM);
float x0 = s + 0f;
float x1 = s + r * fl;
float x2 = s + r + r * fl;
float x3 = w / 2f - r;
float x4 = w / 2f;
float x5 = w / 2f + r;
float x6 = w - 1f - s - r - r * fr;
float x7 = w - 1f - s - r * fr;
float x8 = w - 1f - s;
float y0 = s + 0f;
float y1 = s + r * ft;
float y2 = s + r + r * ft;
float y3 = h / 2f - r;
float y4 = h / 2f;
float y5 = h / 2f + r;
float y6 = h - 1f - s - r - r * fb;
float y7 = h - 1f - s - r * fb;
float y8 = h - 1f - s;
reset();
moveTo(x1, y2);
oval.set(x2 - r, y2 - r, x2 + r, y2 + r);
arcTo(oval, 180f, 90f);
if (ft != 0) {
lineTo(x3, y1);
lineTo(x4, y0);
lineTo(x5, y1);
}
lineTo(x6, y1);
oval.set(x6 - r, y2 - r, x6 + r, y2 + r);
arcTo(oval, 270f, 90f);
if (fr != 0) {
lineTo(x7, y3);
lineTo(x8, y4);
lineTo(x7, y5);
}
lineTo(x7, y6);
oval.set(x6 - r, y6 - r, x6 + r, y6 + r);
arcTo(oval, 0f, 90f);
if (fb != 0) {
lineTo(x5, y7);
lineTo(x4, y8);
lineTo(x3, y7);
}
lineTo(x2, y7);
oval.set(x2 - r, y6 - r, x2 + r, y6 + r);
arcTo(oval, 90f, 90f);
if (fl != 0) {
lineTo(x1, y5);
lineTo(x0, y4);
lineTo(x1, y3);
}
close();
}
public static int factor(int marker, int mask) {
return (marker & mask) != 0 ? 1 : 0;
}
}
Because you have this arc or curv you should create a custom jpg.
But you can use with corners and have below :
You do not need to do it programmatically, you should just use a nine patch image resource that make a perfect image for you. If you want to know more about nine patch and how it works, have a look at this and this links.
As you can see this image makes a perfect chat bubble for you. I already decompiled Whatsapp , Viber and they all using a nine patched image to make a chat bubble. As you can see, the Telegram app also using a nine patched image to achieve this.
My sample chat bubble image:
I also should mention that you don't need any custom path. It is a typical patched image without any customization.
make 9patch image like this so that if in conversation lots of text then also it can be adjusted itself and also not stretch or compressed from curve .
Hope it will help you
From the image you can see that the ball fired on the left that fire behind it, does not match the calculated trajectory. Im drawing the ball trajectory using an equation from a SO question, this is modified to take into consideration the box2d steps of 30 frames per second. This does calculate a valid trajectory but it does not match the actual trajectory of the ball, the ball has a smaller trajectory. I am applying a box2d force to the ball, this also has a density set and a shape. The shape radius varies depending on the type of ball. Im setting the start velocity in the touchdown event.
public class ProjectileEquation {
public float gravity;
public Vector2 startVelocity = new Vector2();
public Vector2 startPoint = new Vector2();
public Vector2 gravityVec = new Vector2(0,-10f);
public float getX(float n) {
return startVelocity.x * (n * 1/30f) + startPoint.x;
}
public float getY(float n) {
float t = 1/30f * n;
return 0.5f * gravity * t * t + startVelocity.y * t + startPoint.y;
}
}
#Override
public void draw(SpriteBatch batch, float parentAlpha) {
float t = 0f;
float width = this.getWidth();
float height = this.getHeight();
float timeSeparation = this.timeSeparation;
for (int i = 0; i < trajectoryPointCount; i+=timeSeparation) {
//projectileEquation.getTrajectoryPoint(this.getX(), this.getY(), i);
float x = this.getX() + projectileEquation.getX(i);
float y = this.getY() + projectileEquation.getY(i);
batch.setColor(this.getColor());
if(trajectorySprite != null) batch.draw(trajectorySprite, x, y, width, height);
// t += timeSeparation;
}
}
public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
if(button==1 || world.showingDialog)return false;
touchPos.set(x, y);
float angle = touchPos.sub(playerCannon.position).angle();
if(angle > 270 ) {
angle = 0;
}
else if(angle >70) {
angle = 70;
}
playerCannon.setAngle(angle);
world.trajPath.controller.angle = angle;
float radians = (float) angle * MathUtils.degreesToRadians;
float ballSpeed = touchPos.sub(playerCannon.position).len()*12;
world.trajPath.projectileEquation.startVelocity.x = (float) (Math.cos(radians) * ballSpeed);
world.trajPath.projectileEquation.startVelocity.y = (float) (Math.sin(radians) * ballSpeed);
return true;
}
public CannonBall(float x, float y, float width, float height, float damage, World world, Cannon cannonOwner) {
super(x, y, width, height, damage, world);
active = false;
shape = new CircleShape();
shape.setRadius(width/2);
FixtureDef fd = new FixtureDef();
fd.shape = shape;
fd.density = 4.5f;
if(cannonOwner.isEnemy) { //Enemy cannon balls cannot hit other enemy cannons just the player
fd.filter.groupIndex = -16;
}
bodyDef.type = BodyType.DynamicBody;
bodyDef.position.set(this.position);
body = world.createBody(bodyDef);
body.createFixture(fd);
body.setUserData(this);
body.setBullet(true);
this.cannonOwner = cannonOwner;
this.hitByBall = null;
this.particleEffect = null;
}
private CannonBall createCannonBall(float radians, float ballSpeed, float radius, float damage)
{
CannonBall cannonBall = new CannonBall(CannonEnd().x, CannonEnd().y, radius * ballSizeMultiplier, radius * ballSizeMultiplier, damage, this.world, this);
cannonBall.velocity.x = (float) (Math.cos(radians) * ballSpeed);
//cannonBall.velocity.x = (float) ((Math.sqrt(10) * Math.sqrt(29) *
// Math.sqrt((Math.tan(cannon.angle)*Math.tan(cannon.angle))+1)) / Math.sqrt(2 * Math.tan(cannon.angle) - (2 * 10 * 2)/29))* -1f;
cannonBall.velocity.y = (float) (Math.sin(radians) * ballSpeed);
cannonBall.active = true;
//cannonBall.body.applyLinearImpulse(cannonBall.velocity, cannonBall.position);
cannonBall.body.applyForce(cannonBall.velocity, cannonBall.position );
return cannonBall;
}
trajPath = new TrajectoryActor(-10f);
trajPath.setX(playerCannon.CannonEnd().x);
trajPath.setY(playerCannon.CannonEnd().y);
trajPath.setWidth(10f);
trajPath.setHeight(10f);
stage.addActor(trajPath);
Here is a code that I used for one of my other games, which proved to be very precise. The trick is to apply the impulse on the body and read the initial velocity. Having that I calculate 10 positions where the body will be within 0.5 seconds. The language is called Squirrel which is Lua based with C/C++ like syntax. You should be able to grasp what is going on there. What returns from the getTrajectoryPointsForObjectAtImpulse is an array of 10 positions through which the ball will pass within 0.5 seconds.
const TIMESTER_DIVIDOR = 60.0;
function getTrajectoryPoint( startingPosition, startingVelocity, n )
{
local gravity = box2DWorld.GetGravity();
local t = 1 / 60.0;
local stepVelocity = b2Vec2.Create( t * startingVelocity.x, t * startingVelocity.y );
local stepGravity = b2Vec2.Create( t * t * gravity.x, t * t * gravity.y );
local result = b2Vec2.Create( 0, 0 );
result.x = ( startingPosition.x + n * stepVelocity.x + 0.5 * ( n * n + n ) * stepGravity.x ) * MTP;
result.y = ( startingPosition.y + n * stepVelocity.y + 0.5 * ( n * n + n ) * stepGravity.y ) * -MTP;
return result;
}
function getTrajectoryPointsForObjectAtImpulse (object, impulse)
{
if( !object || !impulse ) return [];
local result = [];
object.bBody.ApplyLinearImpulse( impulse, object.bBody.GetWorldCenter() );
local initialVelocity = object.bBody.GetLinearVelocity();
object.bBody.SetLinearVelocity( b2Vec2.Create(0, 0) );
object.bBody.SetActive(false);
for ( local i = 0.0 ; i < ( 0.5 * TIMESTER_DIVIDOR ) ; )
{
result.append( getTrajectoryPoint(object.bBody.GetPosition(), initialVelocity, i.tointeger() ) );
i += ( (0.5 * TIMESTER_DIVIDOR) * 0.1 );
}
return result;
}
If you do not understand any part of the code, please let me know and I will try to explain.