本文共 14158 字,大约阅读时间需要 47 分钟。
UI设计师掌握Adobe Photoshop这一强大工具,可以轻松实现将实拍照片转化为素描或水彩画风格的效果。本文将详细介绍实现这一过程的步骤,并提供对应的Android代码示例。
去色,获取黑白图
在Photoshop中,按下快捷键Ctrl+Shift+U可以将图片转换为黑白颜色。每个像素的值将通过公式grey = (int)(r * KR + g * KG + b * KB)计算,最后将灰度值转换为16 bits颜色值。 反相,得到底片效果
使用reverseColor方法,将每个像素取反,模拟照片底片的效果。 高斯模糊,得到反高斯图像
使用高斯模糊算法,平滑像素值,得到反高斯效果的中间结果。颜色减淡,生成素描图
最后通过colorDodge方法,结合反相和高斯模糊的结果,实现颜色的减淡效果。 public static int[] discolor(Bitmap bitmap) { int picHeight = bitmap.getHeight(); int picWidth = bitmap.getWidth(); int[] pixels = new int[picWidth * picHeight]; bitmap.getPixels(pixels, 0, picWidth, 0, 0, picWidth, picHeight); for (int i = 0; i < picHeight; ++i) { for (int j = 0; j < picWidth; ++j) { int index = i * picWidth + j; int color = pixels[index]; int r = (color & 0x00ff0000) >> 16; int g = (color & 0x0000ff00) >> 8; int b = color & 0x000000ff; int grey = (int) (r * 0.393 + g * 0.769 + b * 0.234); pixels[index] = grey < 16 ? (grey < 8 ? grey : grey | 0x00ff0000) : grey | 0xff000000; } } return pixels;} public static int[] reverseColor(int[] pixels) { int length = pixels.length; int[] result = new int[length]; for (int i = 0; i < length; ++i) { int color = pixels[i]; int r = 255 - ((color & 0x00ff0000) >> 16); int g = 255 - ((color & 0x0000ff00) >> 8); int b = 255 - (color & 0x000000ff); result[i] = (r < 16 ? (r < 8 ? r : r | 0x00ff0000) : r | 0xff000000) | (g < 16 ? (g < 8 ? g : g | 0x0000ff00) : g | 0x00ff0000) | (b < 16 ? (b < 8 ? b : b | 0x000000ff) : b | 0x00000000); } return result;} public static void gaussBlur(int[] data, int width, int height, int radius, float sigma) { float pa = 1.0f / (Math.sqrt(2 * Math.PI) * sigma); float pb = -1.0f / (2 * sigma * sigma); float[] gaussMatrix = new float[radius * 2 + 1]; float gaussSum = 0.0f; for (int i = 0, x = -radius; x <= radius; ++i, ++x) { float g = pa * Math.exp(pb * x * x); gaussMatrix[i] = g; gaussSum += g; } for (int i = 0, length = gaussMatrix.length; i < length; ++i) { gaussMatrix[i] /= gaussSum; } for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { float r = 0.0f, g = 0.0f, b = 0.0f; float gaussSum = 0.0f; for (int j = -radius; j <= radius; ++j) { int k = x + j; if (k >= 0 && k < width) { int index = y * width + k; int color = data[index]; int cr = (color & 0x00ff0000) >> 16; int cg = (color & 0x0000ff00) >> 8; int cb = color & 0x000000ff; r += cr * gaussMatrix[j + radius]; g += cg * gaussMatrix[j + radius]; b += cb * gaussMatrix[j + radius]; gaussSum += gaussMatrix[j + radius]; } } int index = y * width + x; float avgR = r / gaussSum; float avgG = g / gaussSum; float avgB = b / gaussSum; data[index] = (int)(avgR < 16 ? (avgR < 8 ? avgR : avgR | 0x00ff0000) : avgR | 0xff000000) | (int)(avgG < 16 ? (avgG < 8 ? avgG : avgG | 0x0000ff00) : avgG | 0x00ff0000) | (int)(avgB < 16 ? (avgB < 8 ? avgB : avgB | 0x000000ff) : avgB | 0x00000000); } } for (int x = 0; x < width; ++x) { for (int y = 0; y < height; ++y) { float r = 0.0f, g = 0.0f, b = 0.0f; float gaussSum = 0.0f; for (int j = -radius; j <= radius; ++j) { int k = y + j; if (k >= 0 && k < height) { int index = k * width + x; int color = data[index]; int cr = (color & 0x00ff0000) >> 16; int cg = (color & 0x0000ff00) >> 8; int cb = color & 0x000000ff; r += cr * gaussMatrix[j + radius]; g += cg * gaussMatrix[j + radius]; b += cb * gaussMatrix[j + radius]; gaussSum += gaussMatrix[j + radius]; } } int index = y * width + x; float avgR = r / gaussSum; float avgG = g / gaussSum; float avgB = b / gaussSum; data[index] = (int)(avgR < 16 ? (avgR < 8 ? avgR : avgR | 0x00ff0000) : avgR | 0xff000000) | (int)(avgG < 16 ? (avgG < 8 ? avgG : avgG | 0x0000ff00) : avgG | 0x00ff0000) | (int)(avgB < 16 ? (avgB < 8 ? avgB : avgB | 0x000000ff) : avgB | 0x00000000); } }} public static void colorDodge(int[] baseColor, int[] mixColor) { for (int i = 0, length = baseColor.length; i < length; ++i) { int bColor = baseColor[i]; int br = (bColor & 0x00ff0000) >> 16; int bg = (bColor & 0x0000ff00) >> 8; int bb = bColor & 0x000000ff; int mColor = mixColor[i]; int mr = (mColor & 0x00ff0000) >> 16; int mg = (mColor & 0x0000ff00) >> 8; int mb = mColor & 0x000000ff; int nr = colorDodgeFormular(br, mr); int ng = colorDodgeFormular(bg, mg); int nb = colorDodgeFormular(bb, mb); baseColor[i] = nr < 16 ? (nr < 8 ? nr : nr | 0x00ff0000) : nr | 0xff000000 | ng < 16 ? (ng < 8 ? ng : ng | 0x0000ff00) : ng | 0x00ff0000 | nb < 16 ? (nb < 8 ? nb : nb | 0x000000ff) : nb | 0x00000000; }}private static int colorDodgeFormular(int base, int mix) { int result = base + (base * mix) / (255 - mix); return result > 255 ? 255 : result;} public static Bitmap testGaussBlur(Bitmap src, int r, int fai) { int width = src.getWidth(); int height = src.getHeight(); int[] pixels = discolor(src); int[] copixels = reverseColor(pixels); gaussBlur(copixels, width, height, r, fai); colorDodge(pixels, copixels); Bitmap bitmap = Bitmap.createBitmap(pixels, width, height, Config.RGB_565); return bitmap;} 为了实现手指左右滑动调节图片透明度的效果,我们可以结合SeekBar和GestureDetector来实现。
public class SeekbarActivity extends AppCompatActivity implements View.OnTouchListener { private static final String TAG = "SeekbarActivity"; private ImageView mImageView, mGestureImageView; private SeekBar mSeekBar; private TextView mTxt; private int mProgress = 100; private float mDistance = 1, maxDistance = 100; private GestureDetector mGestureDetector; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_seekbar); DisplayMetrics displayMetrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); int width = displayMetrics.widthPixels; int height = displayMetrics.heightPixels; mImageView = findViewById(R.id.alpha_img); mGestureImageView = findViewById(R.id.ges_alpha_img); mSeekBar = findViewById(R.id.alpha_seek); mTxt = findViewById(R.id.alpha_txt); mSeekBar.setMax(100); mSeekBar.setProgress(100); mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if (direction == 0) { if ((mProgress - progress) > 0 && (mProgress - progress) < 100) { mImageView.setImageBitmap(testGaussBlur(Utils.setAlpha(finalBitmap, mProgress - progress), 10, 10)); mTxt.setText(String.valueOf(mProgress - progress) + "%"); } else if ((mProgress - progress) < 0) { mImageView.setImageBitmap(testGaussBlur(finalBitmap, 10, 10)); mTxt.setText(String.valueOf(1) + "%"); } } else { if ((mProgress + progress) < 100 && (mProgress + progress) > 0) { mImageView.setImageBitmap(testGaussBlur(Utils.setAlpha(finalBitmap, mProgress + progress), 10, 10)); mTxt.setText(String.valueOf(mProgress + progress) + "%"); } else if ((mProgress + progress) > 100) { mImageView.setImageBitmap(testGaussBlur(finalBitmap, 10, 10)); mTxt.setText(String.valueOf(100) + "%"); } } } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { mProgress = seekBar.getProgress(); } }); mImageView.setImageBitmap(Utils.setAlpha(finalBitmap, 100)); initGesture(); } private void initGesture() { mGestureDetector = new GestureDetector(new simpleGestureListener()); mGestureImageView.setOnTouchListener(this); mGestureImageView.setFocusable(true); mGestureImageView.setClickable(true); mGestureImageView.setLongClickable(true); } @Override public boolean onTouch(View v, MotionEvent event) { return mGestureDetector.onTouchEvent(event); } private int direction = 0; private class simpleGestureListener extends GestureDetector.SimpleOnGestureListener { final int FLING_MIN_DISTANCE = 10; final float MIN_DISTANCE = 0, MAX_DISTANCE = 100; public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { if (Math.abs(distanceX) > FLING_MIN_DISTANCE) { int progress = (int) (maxDistance - Math.abs(distanceX) / 15); if (progress >= MIN_DISTANCE) { mGestureImageView.setImageBitmap(Utils.setAlpha(finalBitmap, progress)); maxDistance = progress; mProgress = progress; mTxt.setVisibility(View.VISIBLE); mTxt.setText(String.valueOf(progress) + "%"); } } else if (Math.abs(distanceX) < -FLING_MIN_DISTANCE) { int progress = (int) (maxDistance - (-distanceX) / 5); if (progress <= MAX_DISTANCE) { mGestureImageView.setImageBitmap(Utils.setAlpha(finalBitmap, progress)); maxDistance = progress; mProgress = progress; mTxt.setVisibility(View.VISIBLE); mTxt.setText(String.valueOf(progress) + "%"); } } return true; } public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { mTxt.setVisibility(View.GONE); mImageView.setImageBitmap(testGaussBlur(finalBitmap, 10, 10)); return true; } }} public class GestureDetectorActivity extends AppCompatActivity implements View.OnTouchListener { private static final String TAG = "GestureDetectorActivity"; private ImageView mImageView; private GestureDetector mGestureDetector; private Bitmap finalBitmap, bitmap; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_gesturedetector); DisplayMetrics displayMetrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); int width = displayMetrics.widthPixels; int height = displayMetrics.heightPixels; mImageView = findViewById(R.id.gesture_img); bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.timg); bitmap = Utils.compressBySampleSize(bitmap, width, height, false); finalBitmap = testGaussBlur(bitmap, 10, 10); mImageView.setImageBitmap(finalBitmap); mGestureDetector = new GestureDetector(new simpleGestureListener()); mImageView.setOnTouchListener(this); mImageView.setFocusable(true); mImageView.setClickable(true); mImageView.setLongClickable(true); } @Override public boolean onTouch(View v, MotionEvent event) { return mGestureDetector.onTouchEvent(event); } private int direction = 0; private class simpleGestureListener extends GestureDetector.SimpleOnGestureListener { final int FLING_MIN_DISTANCE = 10; final float MIN_DISTANCE = 0, MAX_DISTANCE = 100; public boolean onDown(MotionEvent e) { return false; } public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { if (Math.abs(distanceX) > FLING_MIN_DISTANCE) { int progress = (int) (MAX_DISTANCE - Math.abs(distanceX) / 15); if (progress >= MIN_DISTANCE) { mImageView.setImageBitmap(Utils.setAlpha(finalBitmap, progress)); MAX_DISTANCE = progress; direction = distanceX > 0 ? 0 : 1; } } else if (Math.abs(distanceX) < -FLING_MIN_DISTANCE) { int progress = (int) (MAX_DISTANCE - (-distanceX) / 5); if (progress <= MAX_DISTANCE) { mImageView.setImageBitmap(Utils.setAlpha(finalBitmap, progress)); MAX_DISTANCE = progress; direction = distanceX < 0 ? 0 : 1; } } return true; } public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { mImageView.setImageBitmap(finalBitmap); return true; } }} 通过以上代码,我们可以轻松实现将实拍照片转化为素描图的效果,并且通过手指滑动来调节图片的透明度,实现类似调节素描浓度的效果。
转载地址:http://xqjg.baihongyu.com/