Android滤镜效果实现及原理分析

2024-05-04 04:08

本文主要是介绍Android滤镜效果实现及原理分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

https://cloud.tencent.com/developer/article/1038865

 

 

 

Android滤镜效果实现及原理分析

用户1148531发表于向治洪

380

在这篇文章中:

  • 色彩矩阵分析
    • 初始颜色矩阵
  • 图像的色光属性
    • Android实例
    • 常用颜色矩阵
    • GPUImage滤镜

Android在处理图片时,最常使用到的数据结构是位图(Bitmap),它包含了一张图片所有的数据。整个图片都是由点阵和颜色值组成的,所谓点阵就是一个包含像素的矩阵,每一个元素对应着图片的一个像素。而颜色值——ARGB,分别对应着透明度、红、绿、蓝这四个通道分量,他们共同决定了每个像素点显示的颜色。下图是ARGB的模型图。

色彩矩阵分析

在Android中,系统使用一个颜色矩阵-ColorMatrix来处理图像的色彩效果。对于图像的每个像素点,都有一个颜色分量矩阵用来保存颜色的RGBA值(下图矩阵C),Android中的颜色矩阵是一个 4x5 的数字矩阵,它用来对图片的色彩进行处理(下图矩阵A)。

在Android系统中,如果想要改变一张图像的色彩显示效果,可以使用矩阵的乘法运算来修改颜色分量矩阵的值。上面矩阵A就是一个 4x5 的颜色矩阵。在Android中,它会以一维数组的形式来存储[a,b,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t],而C则是一个颜色矩阵分量。在处理图像时,使用矩阵乘法运算AC来处理颜色分量矩阵,如下:

利用线性代数知识可以得到如下等式:

R1 = aR + bG + cB + dA + e;
G1 = fR + gG + hB + iA + j;
B1 = kR + lG + mB + nA + o;
A1 = pR + qG + rB + sA + t;

从上面的等式可以发现:

  • 第一行的 abcde 用来决定新的颜色值中的R——红色
  • 第二行的 fghij 用来决定新的颜色值中的G——绿色
  • 第三行的 klmno 用来决定新的颜色值中的B——蓝色
  • 第四行的 pqrst 用来决定新的颜色值中的A——透明度
  • 矩阵A中第五列——ejot 值分别用来决定每个分量中的 offset ,即偏移量 这样一说明,大家对这个公司就明白了。

初始颜色矩阵

接下来,我们重新看一下矩阵变换的计算公式,以R分量为例。

R1 = aR + bG + cB + dA + e;

如果令 a=1,b、c、d、e都等于0,则有 R1=R 。同理对第二、三、四、行进行操作,可以构造出一个矩阵,如下:

把这个矩阵代入公式 R=AC,根据矩阵乘法运算法则,可得R1=R,G1=G,B1=B,A1=A。即不会对原有颜色进行任何修改,所以这个矩阵通常被用来作为初始颜色矩阵。

改变颜色值

如果想要改变颜色值的时候,通常有两种方法:

  • 改变颜色的 offset(偏移量)的值;
  • 改变对应 RGBA 值的系数。

1.改变偏移量

从前面的分析中可知,改变颜色的偏移量就是改变颜色矩阵的第五列的值,其他保持初始矩阵的值即可。如下示例:

上面的操作中改变了 R、G 对应的颜色偏移量,那么结果就是图像的红色和绿色分量增加了100,即整体色调偏黄显示。

其中,左边为原图,右边为改变 偏移量后的效果。

2.改变颜色系数

假如我们队颜色矩阵做如下操作。

改变 G 分量对应的系数 g 的值,增加到2倍,这样在矩阵运算后,图像会整体色调偏绿显示。

通过前面的分析,我们知道调整颜色矩阵可以改变图像的色彩效果,图像的色彩处理很大程度上就是在寻找处理图像的颜色矩阵。

Android实例

下面,我们着手写一个demo,模拟一个 4x5 的颜色矩阵来体验一下上面对颜色矩阵的分析。效果如下:

关键代码是将 4x5 矩阵转换成一维数组,然后再将这一维数组设置到ColorMatrix类里去,请看代码:

//将矩阵设置到图像private void setImageMatrix() {Bitmap bmp = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);ColorMatrix colorMatrix = new ColorMatrix();colorMatrix.set(mColorMatrix);//将一维数组设置到ColorMatrixCanvas canvas = new Canvas(bmp);Paint paint = new Paint();paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));canvas.drawBitmap(bitmap, 0, 0, paint);iv_photo.setImageBitmap(bmp);}

这个demo里面的代码比较简单,我在这里就全部贴出来了,先上xml布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context="com.example.deeson.mycolormatrix.MainActivity"android:orientation="vertical"><ImageViewandroid:id="@+id/iv_photo"android:layout_width="300dp"android:layout_height="0dp"android:layout_weight="3"android:layout_gravity="center_horizontal"/><GridLayoutandroid:id="@+id/matrix_layout"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="4"android:columnCount="5"android:rowCount="4"></GridLayout><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"><Buttonandroid:id="@+id/btn_change"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:text="change"/><Buttonandroid:id="@+id/btn_reset"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:text="reset"/></LinearLayout></LinearLayout>

在 MainActivity 类这里有一个地方要注意的就是,我们无法在 onCreate() 方法中获得 4x5 矩阵视图的宽高值,所以通过 View 的 post() 方法,在视图创建完毕后获得其宽高值。如下:

matrixLayout.post(new Runnable() {@Overridepublic void run() {mEtWidth = matrixLayout.getWidth() / 5;mEtHeight = matrixLayout.getHeight() / 4;addEts();initMatrix();}});

接下来是 MainActivity 类的全部代码:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {Bitmap bitmap;ImageView iv_photo;GridLayout matrixLayout;//每个edittext的宽高int mEtWidth;int mEtHeight;//保存20个edittextEditText[] mEts = new EditText[20];//一维数组保存20个矩阵值float[] mColorMatrix = new float[20];@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.iv_model);iv_photo = (ImageView) findViewById(R.id.iv_photo);matrixLayout = (GridLayout) findViewById(R.id.matrix_layout);Button btn_change = (Button) findViewById(R.id.btn_change);Button btn_reset = (Button) findViewById(R.id.btn_reset);btn_change.setOnClickListener(this);btn_reset.setOnClickListener(this);iv_photo.setImageBitmap(bitmap);//我们无法在onCreate()方法中获得视图的宽高值,所以通过View的post()方法,在视图创建完毕后获得其宽高值matrixLayout.post(new Runnable() {@Overridepublic void run() {mEtWidth = matrixLayout.getWidth() / 5;mEtHeight = matrixLayout.getHeight() / 4;addEts();initMatrix();}});}//动态添加edittextprivate void addEts() {for (int i = 0; i < 20; i++) {EditText et = new EditText(this);et.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);mEts[i] = et;matrixLayout.addView(et, mEtWidth, mEtHeight);}}//初始化颜色矩阵private void initMatrix() {for (int i = 0; i < 20; i++) {if (i % 6 == 0) {mEts[i].setText(String.valueOf(1));} else {mEts[i].setText(String.valueOf(0));}}}//获取矩阵值private void getMatrix() {for (int i = 0; i < 20; i++) {String matrix = mEts[i].getText().toString();boolean isNone = null == matrix || "".equals(matrix);mColorMatrix[i] = isNone ? 0.0f : Float.valueOf(matrix);if (isNone) {mEts[i].setText("0");}}}//将矩阵设置到图像private void setImageMatrix() {Bitmap bmp = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);ColorMatrix colorMatrix = new ColorMatrix();colorMatrix.set(mColorMatrix);//将一维数组设置到ColorMatrixCanvas canvas = new Canvas(bmp);Paint paint = new Paint();paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));canvas.drawBitmap(bitmap, 0, 0, paint);iv_photo.setImageBitmap(bmp);}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.btn_change://作用矩阵效果break;case R.id.btn_reset://重置矩阵效果initMatrix();break;}//作用矩阵效果getMatrix();setImageMatrix();}
}

如果有人不想自己敲代码的,可以到下面地址下载:Demo下载地址

图像的色光属性

在色彩处理中,通常使用以下三个角度来描述一个图像。

  • 色调:物体传播的颜色
  • 饱和度:颜色的纯度,从0(灰)到100%(饱和)来进行描述
  • 亮度:颜色的相对明暗程度 在Android 的 ColorMatrix 颜色矩阵中也封装了一些 API 来快速调整上面这三个颜色参数,而不用每次都去计算矩阵的值。详情可参考这个文档 :https://developer.android.com/reference/android/graphics/ColorMatrix.html

色调

Android系统提供了 setRotate(int axis, float degrees)方法来修改颜色的色调。第一个参数,用0、1、2分别代表红、绿、蓝三个颜色通道,第二个参数就是要修改的值,如下:

ColorMatrix hueMatrix = new ColorMatrix();
hueMatrix.setRotate(0,hue0);
hueMatrix.setRotate(1,hue1);
hueMatrix.setRotate(2,hue2);

Android系统的 setRotate(int axis, float degrees) 方法其实就是对色彩的旋转运算。RGB色是如何旋转的呢,首先用R、G、B三色建立三维坐标系,如下:

这里,我们把一个色彩值看成三维空间里的一个点,色彩值的三个分量可以看成该点对应的坐标(三维坐标)。先不考虑在三个维度综合情况下是怎么旋转的,我们先看看在某个轴做为Z轴,在另两个轴形成的平面上旋转的情况。假如,我们现在需要围绕蓝色轴进行旋转,我们对着蓝色箭头观察由红色和绿色构造的平面。然后顺时针旋转 α 度。 如下图所示:

在图中,我们可以看到,在旋转后,原 R 在 R 轴的分量变为:R*cosα,且原G分量在旋转后在 R 轴上也有了分量,所以我们要加上这部分分量,因此最终的结果为 R’=R*cosα + G*sinα,同理,在计算 G’ 时,因为 R 的分量落在了负轴上,所以我们要减去这部分,故 G’=G*cosα - R*sinα; 回忆之前讲过的矩阵乘法运算法则,下图:

R1 = aR + bG + cB + dA + e;
G1 = fR + gG + hB + iA + j;
B1 = kR + lG + mB + nA + o;
A1 = pR + qG + rB + sA + t;

可以计算出围绕蓝色分量轴顺时针旋转 α 度的颜色矩阵如下:

同理,可以得出围绕红色分量轴顺时针旋转 α 度的颜色矩阵:

围绕绿色分量轴顺时针旋转 α 度的颜色矩阵:

通过上面的分析,我们可以知道,当围绕红色分量轴进行色彩旋转时,由于当前红色分量轴的色彩是不变的,而仅利用三角函数来动态的变更绿色和蓝色的颜色值。这种改变就叫做色相调节。

当围绕红色分量轴旋转时,是对图片就行红色色相的调节;同理,当围绕蓝色分量轴旋转时,就是对图片就行蓝色色相调节;当然,当围绕绿色分量轴旋转时,就是对图片进行绿色色相的调节。

下面是Android系统对色调修改的源码,我们可以看得到,源码对第二个参数进行转换成弧度,即对红、绿、蓝三个颜色通道分别进行旋转,那我们在第二个参数中传入我们平时用的度数即可。通过对源码的阅读,我们也知道,第二个参数最终被设置的数值范围为 [-1,1] ,然后再设置到颜色矩阵中。即我们在第二个参数传入[-180,180]范围的值(一个最小周期)即可。

下面是上面理论设计到的相关系统源码。

public void setRotate(int axis, float degrees) {reset();double radians = degrees * Math.PI / 180d;float cosine = (float) Math.cos(radians);float sine = (float) Math.sin(radians);switch (axis) {// Rotation around the red colorcase 0:mArray[6] = mArray[12] = cosine;mArray[7] = sine;mArray[11] = -sine;break;// Rotation around the green colorcase 1:mArray[0] = mArray[12] = cosine;mArray[2] = -sine;mArray[10] = sine;break;// Rotation around the blue colorcase 2:mArray[0] = mArray[6] = cosine;mArray[1] = sine;mArray[5] = -sine;break;default:throw new RuntimeException();}}

饱和度

Android系统提供了 setSaturation(float sat) 方法来修改颜色的饱和度。参数 float sat:表示把当前色彩饱和度放大的倍数。取值为0表示完全无色彩,即灰度图像(黑白图像);取值为1时,表示色彩不变动;当取值大于1时,显示色彩过度饱和 如下:

ColorMatrix saturationMatrix = new ColorMatrix();
saturationMatrix.setSaturation(saturation);

同样贴出修改饱和度值的源码,通过源码我们可以看到系统是通过改变颜色矩阵中对角线上系数的比例来改变饱和度。系统相关源码如下:

public void setSaturation(float sat) {reset();float[] m = mArray;final float invSat = 1 - sat;final float R = 0.213f * invSat;final float G = 0.715f * invSat;final float B = 0.072f * invSat;m[0] = R + sat; m[1] = G;       m[2] = B;m[5] = R;       m[6] = G + sat; m[7] = B;m[10] = R;      m[11] = G;      m[12] = B + sat;}

亮度

当三原色以相同比例进行混合时,就会显示出白色。Android系统正是利用这个原理对图像进行亮度的改变。如下:

ColorMatrix lumMatrix = new ColorMatrix();
lumMatrix.setScale(lum,lum,lum,1);

同样贴出修改亮度值的源码,当亮度为 0 时,图像就变成全黑了。通过对源码的阅读,我们可以知道, 系统将颜色矩阵置为初始初始颜色矩阵,再将红、绿、蓝、透明度四个分量通道对应的系数修改成我们传入的值。

public void setScale(float rScale, float gScale, float bScale,float aScale) {final float[] a = mArray;for (int i = 19; i > 0; --i) {a[i] = 0;}a[0] = rScale;a[6] = gScale;a[12] = bScale;a[18] = aScale;}

当然,除了单独使用上面的三种方法来进行颜色效果的处理之外,Android系统还封装了矩阵的乘法运算。它提供了 postConcat(ColorMatrix postmatrix) 方法来将矩阵的作用效果混合,从而叠加处理效果。如下:

ColorMatrix imageMatrix = new ColorMatrix();
imageMatrix.postConcat(hueMatrix);
imageMatrix.postConcat(saturationMatrix);
imageMatrix.postConcat(lumMatrix);

Android实例

下面,通过一个demo来给大家看看,修改色调、饱和度、亮度的效果。 首先我们看看效果图,如下:

这里的 demo 通过滑动三个 SeekBar 来改变不同的值,并将这些数值作用到对应色调、饱和度、亮度的颜色矩阵中,最后通过 ColorMatrix 的 postConcat() 方法来混合这三个被修改的颜色矩阵的显示效果。相关代码如下:

public static Bitmap handleImageEffect(Bitmap oriBmp, Bitmap bmp, float hue, float saturation, float lum) {Canvas canvas = new Canvas(bmp);Paint paint = new Paint();ColorMatrix hueMatrix = new ColorMatrix();hueMatrix.setRotate(0, hue);hueMatrix.setRotate(1, hue);hueMatrix.setRotate(2, hue);ColorMatrix saturationMatrix = new ColorMatrix();saturationMatrix.setSaturation(saturation);ColorMatrix lumMatrix = new ColorMatrix();lumMatrix.setScale(lum, lum, lum, 1);ColorMatrix imageMatrix = new ColorMatrix();imageMatrix.postConcat(hueMatrix);imageMatrix.postConcat(saturationMatrix);imageMatrix.postConcat(lumMatrix);paint.setColorFilter(new ColorMatrixColorFilter(imageMatrix));canvas.drawBitmap(oriBmp, 0, 0, paint);return bmp;}

Android系统不允许直接修改原图,类似 Photoshop 中的锁定,必须通过原图创建一个同样大小的 Bitmap ,并将原图绘制到该 Bitmap 中,以一个副本的形式来修改图像。 在设置好需要处理的颜色矩阵后,通过使用 Paint 类的 setColorFilter() 方法,将通过 imageMatrix 构造的 ColorMatrixColorFilter 对象传递进去,并使用这个画笔来绘制原来的图像,从而将颜色矩阵作用到图像中。 下面是布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context="com.example.deeson.mycolor.MainActivity"android:orientation="vertical"><ImageViewandroid:id="@+id/iv_photo"android:layout_width="300dp"android:layout_height="300dp"android:layout_gravity="center_horizontal"android:layout_marginTop="20dp"android:src="@drawable/iv_model0"/><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="20dp"android:layout_marginLeft="5dp"android:textColor="@android:color/black"android:text="色调"/><SeekBarandroid:id="@+id/seekbarHue"android:layout_width="match_parent"android:layout_height="wrap_content"android:max="200"android:progress="100"/><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="10dp"android:layout_marginLeft="5dp"android:textColor="@android:color/black"android:text="饱和度"/><SeekBarandroid:id="@+id/seekbarSaturation"android:layout_width="match_parent"android:layout_height="wrap_content"android:max="200"android:progress="100"/><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="10dp"android:layout_marginLeft="5dp"android:textColor="@android:color/black"android:text="亮度"/><SeekBarandroid:id="@+id/seekbarLum"android:layout_width="match_parent"android:layout_height="wrap_content"android:max="200"android:progress="100"/></LinearLayout>

然后是 MainActivity 类的代码,就是获取三个 SeekBar 的值。

public class MainActivity extends AppCompatActivity implements SeekBar.OnSeekBarChangeListener {ImageView iv_photo;float mHue = 0.0f;float mSaturation = 1f;float mLum = 1f;float MID_VALUE;Bitmap oriBitmap,newBitmap;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);iv_photo = (ImageView) findViewById(R.id.iv_photo);SeekBar barHue = (SeekBar) findViewById(R.id.seekbarHue);SeekBar barSaturation = (SeekBar) findViewById(R.id.seekbarSaturation);SeekBar barLum = (SeekBar) findViewById(R.id.seekbarLum);MID_VALUE = barHue.getMax() * 1.0F / 2;oriBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.iv_model0);//Android系统不允许直接修改原图newBitmap = Bitmap.createBitmap(oriBitmap.getWidth(), oriBitmap.getHeight(), Bitmap.Config.ARGB_8888);barHue.setOnSeekBarChangeListener(this);barSaturation.setOnSeekBarChangeListener(this);barLum.setOnSeekBarChangeListener(this);}@Overridepublic void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {switch (seekBar.getId()) {case R.id.seekbarHue:mHue = (progress - MID_VALUE) * 1.0F / MID_VALUE * 180;break;case R.id.seekbarSaturation:mSaturation = progress * 1.0F / MID_VALUE;break;case R.id.seekbarLum:mLum = progress * 1.0F / MID_VALUE;break;}iv_photo.setImageBitmap(ImageHelper.handleImageEffect(oriBitmap,newBitmap, mHue, mSaturation, mLum));}@Overridepublic void onStartTrackingTouch(SeekBar seekBar) {}@Overridepublic void onStopTrackingTouch(SeekBar seekBar) {}
}

代码Demo 其实讲到这里,大家对颜色矩阵和滤镜的实现原理有一个大概的了解了吧。

常用颜色矩阵

灰度效果

图像反转

效果如下:

怀旧效果

效果如下:

去色效果

效果如下:

高饱和度

效果如下:

色彩反色

这里是红绿反色,另外红蓝、蓝绿反色原理一样,就是把颜色初始矩阵中对应颜色通道的值交换处理,如下:

GPUImage滤镜

GPUImage是一个专门做滤镜和帖纸的开源库,详细资料就不介绍了,给大家提供一个我开源的使用例子。

项目源码:https://github.com/xiangzhihong/gpuImage

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于 2018-02-06

Android

举报

6

分享

  •  

  •  
  •  
  •  

 

向治洪

1009 篇文章93 人订阅

  • React Native 0.50版本新功能简介
  • 基于OpenCV的跳一跳外挂实现原理
  • OSGi简介
  • Spring Boot入门及整合mybatis
  • Swift开发React Native组件

我来说两句

0 条评论

登录 后参与评论

  • 上一篇:React Native 0.50版本新功能简介
  • 下一篇:Spring Boot入门及整合mybatis

相关文章

来自专栏郭耀华‘s Blog

android:scaleType属性

ImageView.ScaleType.CENTER|android:scaleType="center" 以原图的几何中心点和ImagView的几何中心点为...

2609

 

这篇关于Android滤镜效果实现及原理分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/958306

相关文章

基于SpringBoot+Mybatis实现Mysql分表

《基于SpringBoot+Mybatis实现Mysql分表》这篇文章主要为大家详细介绍了基于SpringBoot+Mybatis实现Mysql分表的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可... 目录基本思路定义注解创建ThreadLocal创建拦截器业务处理基本思路1.根据创建时间字段按年进

SpringBoot3实现Gzip压缩优化的技术指南

《SpringBoot3实现Gzip压缩优化的技术指南》随着Web应用的用户量和数据量增加,网络带宽和页面加载速度逐渐成为瓶颈,为了减少数据传输量,提高用户体验,我们可以使用Gzip压缩HTTP响应,... 目录1、简述2、配置2.1 添加依赖2.2 配置 Gzip 压缩3、服务端应用4、前端应用4.1 N

Java编译生成多个.class文件的原理和作用

《Java编译生成多个.class文件的原理和作用》作为一名经验丰富的开发者,在Java项目中执行编译后,可能会发现一个.java源文件有时会产生多个.class文件,从技术实现层面详细剖析这一现象... 目录一、内部类机制与.class文件生成成员内部类(常规内部类)局部内部类(方法内部类)匿名内部类二、

Go标准库常见错误分析和解决办法

《Go标准库常见错误分析和解决办法》Go语言的标准库为开发者提供了丰富且高效的工具,涵盖了从网络编程到文件操作等各个方面,然而,标准库虽好,使用不当却可能适得其反,正所谓工欲善其事,必先利其器,本文将... 目录1. 使用了错误的time.Duration2. time.After导致的内存泄漏3. jsO

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

Python FastAPI+Celery+RabbitMQ实现分布式图片水印处理系统

《PythonFastAPI+Celery+RabbitMQ实现分布式图片水印处理系统》这篇文章主要为大家详细介绍了PythonFastAPI如何结合Celery以及RabbitMQ实现简单的分布式... 实现思路FastAPI 服务器Celery 任务队列RabbitMQ 作为消息代理定时任务处理完整

Java枚举类实现Key-Value映射的多种实现方式

《Java枚举类实现Key-Value映射的多种实现方式》在Java开发中,枚举(Enum)是一种特殊的类,本文将详细介绍Java枚举类实现key-value映射的多种方式,有需要的小伙伴可以根据需要... 目录前言一、基础实现方式1.1 为枚举添加属性和构造方法二、http://www.cppcns.co

使用Python实现快速搭建本地HTTP服务器

《使用Python实现快速搭建本地HTTP服务器》:本文主要介绍如何使用Python快速搭建本地HTTP服务器,轻松实现一键HTTP文件共享,同时结合二维码技术,让访问更简单,感兴趣的小伙伴可以了... 目录1. 概述2. 快速搭建 HTTP 文件共享服务2.1 核心思路2.2 代码实现2.3 代码解读3.

Android中Dialog的使用详解

《Android中Dialog的使用详解》Dialog(对话框)是Android中常用的UI组件,用于临时显示重要信息或获取用户输入,本文给大家介绍Android中Dialog的使用,感兴趣的朋友一起... 目录android中Dialog的使用详解1. 基本Dialog类型1.1 AlertDialog(

MySQL双主搭建+keepalived高可用的实现

《MySQL双主搭建+keepalived高可用的实现》本文主要介绍了MySQL双主搭建+keepalived高可用的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,... 目录一、测试环境准备二、主从搭建1.创建复制用户2.创建复制关系3.开启复制,确认复制是否成功4.同