博客
关于我
Android素描算法及实现手指在图片上左右滑动调节图片透明度,最终实现类似调节素描浓度的效果
阅读量:373 次
发布时间:2019-03-05

本文共 21327 字,大约阅读时间需要 71 分钟。

一、前期基础知识储备

UI掌握PS这一逆天的软件,可以实现将图片转化为素描或者水彩的效果,以为例:

  1. 在Photoshop中打开一张人物照片,按下快捷键“Ctrl+Shift+U”,把它转换成黑白颜色;
  2. 复制图层,得到一个副本图层。按下快捷键“Ctrl+I”,将副本图层转换成负片效果;
  3. 将副本图层下拉菜单选为“颜色减淡”,这时图片会亮得几乎什么也看不见,不要急,慢慢来;
  4. 在“滤镜”菜单下选择“模糊→高斯模糊”,模糊半径值可根据你需要的素描线条粗细深浅来设置。到此素描画像工作就完成了。

我们将以上四步进行抽象,得到将图片转为素描效果的步骤即为:

  1. 去色,将图片变为灰度图,即黑白图;
  2. 反相,得到每个像素的补色,具体效果就像照片的底片;
  3. 高斯模糊,把反相后的像素值平均一下;
  4. 颜色减淡,将第1步中的像素和第3步得到的像素值进行计算。

在Android图像处理领域,我们可以使用像素点分析的方法实现上述的效果组合。下面,用代码实现上述过程

二、上代码,具体实现素描算法

1)去色,获取黑白图;

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 * KR + g * KG + b * KB);				pixels[index] = grey << 16 | grey << 8 | grey | 0xff000000;			}		}				return pixels; 	}

2)反相,得到图片的底图;

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 | g << 8 | b | 0xff000000;		}		return result;			}

3)高斯模糊,得到反高斯图像;

public static void gaussBlur(int[] data, int width, int height, int radius,			float sigma) { 		float pa = (float) (1 / (Math.sqrt(2 * Math.PI) * sigma));		float pb = -1.0f / (2 * sigma * sigma); 		// generate the Gauss Matrix		float[] gaussMatrix = new float[radius * 2 + 1];		float gaussSum = 0f;		for (int i = 0, x = -radius; x <= radius; ++x, ++i) {			float g = (float) (pa * Math.exp(pb * x * x));			gaussMatrix[i] = g;			gaussSum += g;		} 		for (int i = 0, length = gaussMatrix.length; i < length; ++i) {			gaussMatrix[i] /= gaussSum;		} 		// x direction		for (int y = 0; y < height; ++y) {			for (int x = 0; x < width; ++x) {				float r = 0, g = 0, b = 0;				gaussSum = 0;				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;				int cr = (int) (r / gaussSum);				int cg = (int) (g / gaussSum);				int cb = (int) (b / gaussSum);								data[index] = cr << 16 | cg << 8 | cb | 0xff000000;			}		} 		// y direction		for (int x = 0; x < width; ++x) {			for (int y = 0; y < height; ++y) {				float r = 0, g = 0, b = 0;				gaussSum = 0;				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;				int cr = (int) (r / gaussSum);				int cg = (int) (g / gaussSum);				int cb = (int) (b / gaussSum);				data[index] = cr << 16 | cg << 8 | cb | 0xff000000;			}		}	}

4)淡化颜色,生成Sketch图

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 | ng << 8 | nb | 0xff000000;		} 	} 	private static int colorDodgeFormular(int base, int mix) {				int result = base + (base * mix) / (255 - mix);		result = result > 255 ? 255 : result;		return result;			}

5)将上述代码封如工具类中,最后定义一个获取素描图的方法

public static Bitmap testGaussBlur(Bitmap src, int r, int fai) { 		int width = src.getWidth();		int height = src.getHeight(); 		int[] pixels = Sketch.discolor(src);		int[] copixels = Sketch.simpleReverseColor(pixels);		Sketch.simpleGaussBlur(copixels, width, height, r, fai);		Sketch.simpleColorDodge(pixels, copixels); 		Bitmap bitmap = Bitmap.createBitmap(pixels, width, height,				Config.RGB_565);		return bitmap; 	}

外界,调用时,传入两个int类型的参数即可,表示高斯模糊的程度和半径。

public class SketchActivity extends AppCompatActivity {    private static final String TAG = "SketchActivity";    @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_sketch);        DisplayMetrics displayMetrics = new DisplayMetrics();        getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);        int width = displayMetrics.widthPixels;        int height = displayMetrics.heightPixels;        ImageView imageView = findViewById(R.id.test_img);        Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.timg);        // 对大图进行压缩 传入的参数为手机屏幕的尺寸 工具类在文末给出        bitmap = Utils.compressBySampleSize(bitmap, width, height, false);        Bitmap bitmap = SketchUtil.testGaussBlur(bitmap,10,10);        imageView.setImageBitmap(bitmap); // 素描图    }    @Override    protected void onDestroy() {        super.onDestroy();    }}

效果如下:

三、上代码,具体实现手指在图片上左右滑动调节图片透明度,实现类似调节素描浓度的效果

在上面的算法中,我们为testGaussBlur()传入两个参数,即可实现调节素描的效果。现在提出另一个思路,就是在不改变已传入参数的前提下,调节素描的浓度:

要求:手指左滑浓度变淡,即素描图变透明;手指右滑变浓,即凸显素描图

① 我们在原有ImageView的位置放入一个FrameLayout,然后在这个FrameLayout中放入两个ImageView,一个用于放置原图,在底层;一个用于放置素描效果图,在上层。两个ImageView的大小设为一样大,由于FrameLayout的特性,我们只能看见效果图。

② 然后我们为上层放置效果图的ImageView写入手势控制事件,当用户在图片上左右移动手指的时候,就调节上层图片的透明度,这样就达到了类似调节素描浓度的效果。

实现如下:

/** * 作者    cpf * 时间    2019/4/16 * 文件    TestApplication * 描述    手指在图片上滑动 可调节图片的透明度 * ①使用Seekbar是否可以做到? 没有办法区分左右滑动 记录上次滑动结果可以做的到 在onStopTrackingTouch方法中记录 * ②自定义View + 手势移动 onFling没法表示中间的过程 只有起始和结束时的状态 ,需要一个渐变的距离 onScroll */public class SeekbarActivity extends AppCompatActivity implements View.OnTouchListener {    private static final String TAG = "SeekbarActivity";    private ImageView mImageView, mGestureImageView, originImageView;    private SeekBar mSeekBar;    private TextView mTxt, mGesTxt;    private int mProgress = 100;    private float mDistance = 1, maxDistance = 100; // 控制progress参数在0-100,0为完全透明 100为起始值可见    private GestureDetector mGestureDetector;    private Bitmap finalBitmap,bitmap;    @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);        originImageView = findViewById(R.id.origin_img);        mSeekBar = findViewById(R.id.alpha_seek);        mTxt = findViewById(R.id.alpha_txt);        mGesTxt = findViewById(R.id.ges_alpha_txt);        bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.timg);        bitmap = Utils.compressBySampleSize(bitmap, width, height, false);        finalBitmap = SketchUtil.testGaussBlur(bitmap,10,10);        mImageView.setImageBitmap(bitmap);        mGestureImageView.setImageBitmap(finalBitmap);        originImageView.setImageBitmap(bitmap);        mSeekBar.setMax(100); // 100 代表完不全透明 0 代表完全透明        mSeekBar.setProgress(100);        mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {            @Override            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {                Log.d(TAG, "onProgressChanged: direction..." + direction);                if (direction == 0) {                    // 左滑 变透明                    if ((mProgress - progress) > 0 && (mProgress - progress) < 100) {                        mImageView.setImageBitmap(SketchUtil.testGaussBlur(Utils.setAlpha(finalBitmap, (mProgress - progress)),10,10));                        mTxt.setText(String.valueOf((mProgress - progress)) + "%");                        Log.d(TAG, "onProgressChanged: 左滑," + (mProgress - progress));                    } else if ((mProgress - progress) < 0) {                        mImageView.setImageBitmap(Utils.setAlpha(SketchUtil.testGaussBlur(finalBitmap,10,10), 1));                        mTxt.setText(String.valueOf(1) + "%");                        Log.d(TAG, "onProgressChanged: 左滑过界," + 1);                    }                } else {                    // 右滑 变清晰                    if ((mProgress + progress) < 100 && (mProgress + progress) > 0) {                        mImageView.setImageBitmap(SketchUtil.testGaussBlur(Utils.setAlpha(finalBitmap, (mProgress + progress)),10,10));                        mTxt.setText(String.valueOf((mProgress + progress)) + "%");                        Log.d(TAG, "onProgressChanged: 右滑," + (mProgress + progress));                    } else if ((mProgress + progress) > 100) {                        mImageView.setImageBitmap(Utils.setAlpha(SketchUtil.testGaussBlur(finalBitmap,10,10), 100));                        mTxt.setText(String.valueOf(100) + "%");                        Log.d(TAG, "onProgressChanged: 右滑过界," + 100);                    }                }            }            @Override            public void onStartTrackingTouch(SeekBar seekBar) {            }            @Override            public void onStopTrackingTouch(SeekBar seekBar) {                mTxt.setVisibility(View.GONE);                mProgress = seekBar.getProgress();                Log.d(TAG, "onProgressChanged, onStopTrackingTouch:::" + mProgress);            }        });        //初始化图片        mImageView.setImageBitmap(Utils.setAlpha(finalBitmap, 100)); // 0 代表完全透明        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) {        // TODO Auto-generated method stub        return mGestureDetector.onTouchEvent(event);    }    private int direction = 0;    private class simpleGestureListener extends            GestureDetector.SimpleOnGestureListener {        /*****OnGestureListener的函数*****/        final int FLING_MIN_DISTANCE = 10;        final float MIN_DISTANCE = 0, MAX_DISTANCE = 100;        public boolean onDown(MotionEvent e) {            // do nothing            return false;        }        public boolean onScroll(MotionEvent e1, MotionEvent e2,                                float distanceX, float distanceY) {            if (e1.getX() - e2.getX() > FLING_MIN_DISTANCE) {                // Fling left 左滑变透明 distanceX为正值                int progress = (int) (maxDistance - distanceX / 15);                if (progress >= MIN_DISTANCE) {                    mGestureImageView.setImageBitmap(Utils.setAlpha(finalBitmap, progress));                    maxDistance = progress;                    mProgress = progress;                    mGesTxt.setVisibility(View.VISIBLE);                    mGesTxt.setText(String.valueOf(progress) + "%");                    Log.d("MyGesture22", "onScroll:" + progress + ", 左滑 ," + distanceX);                }            } else if (e2.getX() - e1.getX() > FLING_MIN_DISTANCE) {                // Fling right 右滑显示 distanceX为负值                int progress = (int) (maxDistance - distanceX / 5);                if (progress <= MAX_DISTANCE) {                    mGestureImageView.setImageBitmap(Utils.setAlpha(finalBitmap, progress));                    maxDistance = progress;                    mProgress = progress;                    mGesTxt.setVisibility(View.VISIBLE);                    mGesTxt.setText(String.valueOf(progress) + "%");                    Log.d("MyGesture22", "onScroll:" + progress + ", 右滑 ," + distanceX);                }            }            return true;        }        // 用户按下触摸屏、快速移动后松开,由1个MotionEvent ACTION_DOWN, 多个ACTION_MOVE, 1个ACTION_UP触发        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,                               float velocityY) {            mGesTxt.setVisibility(View.GONE);            mImageView.setImageBitmap(Utils.xFerMode(bitmap,Utils.setAlpha(finalBitmap, mProgress)));            Log.d(TAG, "onFling: " + mProgress);            return true;        }    }}

可以看到,笔者先是尝试使用SeekBar的方式来实现类似的效果,即将该SeekBar置于图片的上层,然后重写其两个属性,将其设为完全透明,即对于用户不可见,同时将SeekBar设为大小同ImageView一样大,也就实现了对于图片的覆盖,然后在对SeekBar的滑动监听事件onProgressChanged()中调节效果图的透明度,同时在另一监听方法onStopTrackingTouch()记录手指上次离开屏幕时的数值,这样在下次手指down下的时候,传入该值即可。用SeekBar实现这一需求只能做到一半,如上面代码中的注视一般,可以做到记录上次滑动的数值,但是没有办法区分开手指滑动的方向,即无法验证手指左滑还是右滑。

而后,笔者采用GestureDetector的方式实现,拦截下屏幕上的触摸事件,并把事件设置给展示效果图的ImageView。这样手指滑动的事件就传递给了ImageView。然后在重写onScroll()方法,区分开手指左右滑动,并在对应的实现中对图片透明度做不同的调整;最后重写onFling()方法,识别手指抬起,将展示图片透明度数值的TextView设为不可见。

效果如下:

以下是文中使用到的工具类:用以加载大图、调节图片透明度。还有其他一些关于Bitmap的处理方法。

public class Utils {    public static final String TAG = "Utils:";    private static final String SAVE_FOLDER = "PencilCamera";    private static final String SAVE_FILENAME_PREFIX = "IMG";    private static int lastSaveFileIndex = 0;    /**     * 图片透明度处理     *     * @param sourceImg 原始图片     * @param number    透明度     * @return     */    public static Bitmap setAlpha(Bitmap sourceImg, int number) {        try {            int[] argb = new int[sourceImg.getWidth() * sourceImg.getHeight()];            sourceImg.getPixels(argb, 0, sourceImg.getWidth(), 0, 0,                    sourceImg.getWidth(), sourceImg.getHeight());// 获得图片的ARGB值            number = number * 255 / 100;            for (int i = 0; i < argb.length; i++) {                if ((argb[i] & 0xff000000) != 0x00000000) {// 透明色不做处理                    argb[i] = (number << 24) | (argb[i] & 0xFFFFFF);// 修改最高2位的值                }            }            sourceImg = Bitmap.createBitmap(argb, sourceImg.getWidth(),                    sourceImg.getHeight(), Bitmap.Config.ARGB_8888);        } catch (OutOfMemoryError e) {            e.printStackTrace();            System.gc();        }        return sourceImg;    }    /**     * 混合两张Bitmap 返回融合后的Bitmap     *     * @param src 主图     * @param dst 修饰图     */    public static Bitmap xFerMode(Bitmap src, Bitmap dst) {        Bitmap lightenModeBitmap = Bitmap.createBitmap(dst.getWidth(), dst.getHeight(), Bitmap.Config.ARGB_8888);        Canvas canvas = new Canvas(lightenModeBitmap);        Paint paint1 = new Paint();        paint1.setAntiAlias(true);        Rect srcRect = new Rect(0, 0, src.getWidth(), src.getHeight());        canvas.drawARGB(0, 0, 0, 0);        canvas.drawBitmap(src, srcRect, srcRect, paint1); //画人物 src        paint1.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.LIGHTEN));        // 再把原来的bitmap画到现在的bitmap        canvas.drawBitmap(dst, srcRect, srcRect, paint1); //画特效 dst        return lightenModeBitmap;    }    /**     * Return the compressed bitmap using sample size.     *     * @param src       The source of bitmap.     * @param maxWidth  The maximum width.     * @param maxHeight The maximum height.     * @param recycle   True to recycle the source of bitmap, false otherwise.     * @return the compressed bitmap     */    public static Bitmap compressBySampleSize(final Bitmap src,                                              final int maxWidth,                                              final int maxHeight,                                              final boolean recycle) {        if (isEmptyBitmap(src)) return null;        BitmapFactory.Options options = new BitmapFactory.Options();        options.inJustDecodeBounds = true;        ByteArrayOutputStream baos = new ByteArrayOutputStream();        src.compress(Bitmap.CompressFormat.JPEG, 100, baos);        byte[] bytes = baos.toByteArray();        BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);        options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);        options.inJustDecodeBounds = false;        if (recycle && !src.isRecycled()) src.recycle();        return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);    }    /**     * Return the sample size.     * 1500*1500的大图采样率为8 尺寸为562*562     * 300*400的采样率为2     * @param options   The options.     * @param maxWidth  The maximum width.     * @param maxHeight The maximum height.     * @return the sample size     */    private static int calculateInSampleSize(final BitmapFactory.Options options,                                             final int maxWidth,                                             final int maxHeight) {        int height = options.outHeight;        int width = options.outWidth;        int inSampleSize = 1;        while (height > maxHeight || width > maxWidth) {            height >>= 1;            width >>= 1;            inSampleSize <<= 1;        }        return inSampleSize;    }    private static boolean isEmptyBitmap(final Bitmap src) {        return src == null || src.getWidth() == 0 || src.getHeight() == 0;    }    /**     * Reize bitmap with dimensions equal to or less than given params, without changing the aspect ratio     * @param bitmap    Input bitmap     * @param maxWidth  Max allowed width of resized Bitmap     * @param maxHeight Max allowed height of resized Bitmap     * @return Resized bitmap     */    public static Bitmap resizeBitmap(Bitmap bitmap, int maxWidth, int maxHeight) {        if (maxHeight > 0 && maxWidth > 0) {            int width = bitmap.getWidth();            int height = bitmap.getHeight();            float ratioBitmap = (float) width / (float) height;            float ratioMax = (float) maxWidth / (float) maxHeight;            int finalWidth = maxWidth;            int finalHeight = maxHeight;            if (ratioMax > ratioBitmap) {                finalWidth = (int) ((float)maxHeight * ratioBitmap);            } else {                finalHeight = (int) ((float)maxWidth / ratioBitmap);            }            Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, finalWidth, finalHeight, true);            return scaledBitmap;        } else {            return bitmap;        }    }    /**     * Save bitmap as JPEG with a incremental filename, in the SAVE_FOLDER directory     * @param activity     * @param bitmap     * @return     * @throws IOException     */    public static String saveBitmap(Activity activity, Bitmap bitmap) throws IOException {        File file;        try {            // Create a new save file            file = createSaveFile();            OutputStream fOut = new FileOutputStream(file);            // saving the Bitmap to a file compressed as a JPEG with 85% compression rate            bitmap.compress(Bitmap.CompressFormat.JPEG, 85, fOut);            fOut.close();            // Add saved file to gallery            Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);            Uri contentUri = Uri.fromFile(file);            mediaScanIntent.setData(contentUri);            activity.sendBroadcast(mediaScanIntent);        } catch(IOException e) {            String errorMsg = "Unable to save image";            Log.e(TAG, errorMsg + ":" + e.getMessage());            throw new IOException(errorMsg);        }        return file.getPath();    }    /**     * Rotate bitmap by given angle in degrees     * @param bitmap     * @param angle     * @return     */    public static Bitmap rotateBitmap(Bitmap bitmap, int angle) {        Matrix matrix = new Matrix();        matrix.postRotate(angle);        return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);    }    /**     * Create a new save file in the SAVE_FOLDER directory with name as '
_<3 digit serial number>' * ( example: IMG_001) * @return * @throws IOException */ private static File createSaveFile() throws IOException { File file; String path = Environment.getExternalStorageDirectory().toString(); OutputStream fOut = null; String saveFolderPath = path + '/' + SAVE_FOLDER; int saveFileIndex = lastSaveFileIndex; do { String saveFileName = SAVE_FILENAME_PREFIX + String.format("%03d", ++saveFileIndex) + ".jpg"; String saveFilePath = saveFolderPath + '/' + saveFileName; Log.d(TAG,"Saving image to path - "+saveFilePath); file = new File(saveFilePath); // the File to save to file.getParentFile().mkdirs(); } while(file.exists()); file.createNewFile(); return file; }}

 

 

转载地址:http://xqjg.baihongyu.com/

你可能感兴趣的文章
MySQL8.0.29启动报错Different lower_case_table_names settings for server (‘0‘) and data dictionary (‘1‘)
查看>>
MYSQL8.0以上忘记root密码
查看>>
Mysql8.0以上重置初始密码的方法
查看>>
mysql8.0新特性-自增变量的持久化
查看>>
Mysql8.0注意url变更写法
查看>>
Mysql8.0的特性
查看>>
MySQL8修改密码报错ERROR 1819 (HY000): Your password does not satisfy the current policy requirements
查看>>
MySQL8修改密码的方法
查看>>
Mysql8在Centos上安装后忘记root密码如何重新设置
查看>>
Mysql8在Windows上离线安装时忘记root密码
查看>>
MySQL8找不到my.ini配置文件以及报sql_mode=only_full_group_by解决方案
查看>>
mysql8的安装与卸载
查看>>
MySQL8,体验不一样的安装方式!
查看>>
MySQL: Host '127.0.0.1' is not allowed to connect to this MySQL server
查看>>
Mysql: 对换(替换)两条记录的同一个字段值
查看>>
mysql:Can‘t connect to local MySQL server through socket ‘/var/run/mysqld/mysqld.sock‘解决方法
查看>>
MYSQL:基础——3N范式的表结构设计
查看>>
MYSQL:基础——触发器
查看>>
Mysql:连接报错“closing inbound before receiving peer‘s close_notify”
查看>>
mysqlbinlog报错unknown variable ‘default-character-set=utf8mb4‘
查看>>