结合Matrix实现ZoomImageView,包括双击缩放,多点缩放。
2016-02-12 20:13
393 查看
Matrix是一个包含图像信息的3X3的矩阵,包括图片的以下四个信息,可以通过对矩阵中相应的数值进行操作
Translate 平移变换
Rotate 旋转变换
Scale 缩放变换
Skew 错切变换
在代码中使用一个数组来表示这9个数值。
我写的ZoomImageView主要涉及到了缩放、平移,matrix类中有postTranslate(float dx,float dy);postScale(float dx,float dy)等post方法可以对数组的相应数值进行后乘操作;以及mMatrix.setScale( float sx, float sy, float px, float py)等set方法对矩阵中相应的数值进行直接操作,但是这里有一个问题:
以倍数为例
**setScale( float sx, float sy, float px, float py)方法指定的中心并不精确,而postScale放大的倍数并不精确,**postScale只能在原有基础上放大指定的倍数,如果需要实现动画效果,则需要指定一个与1接近的参数不停的执行后乘方法,直到超过目标倍数,这个结果最后也不精确,所以我想到了属性动画中的数值发生器 ,这样既实现了动画效果,也保证了结果的精确,比较难的就是需要计算最终状态的倍数。最终的位移值也需要计算,从而实现以指定的点为中心放大图片。
以下为代码:(主要就是计算过程以及各种状态的判断比较复杂)
Translate 平移变换
Rotate 旋转变换
Scale 缩放变换
Skew 错切变换
在代码中使用一个数组来表示这9个数值。
我写的ZoomImageView主要涉及到了缩放、平移,matrix类中有postTranslate(float dx,float dy);postScale(float dx,float dy)等post方法可以对数组的相应数值进行后乘操作;以及mMatrix.setScale( float sx, float sy, float px, float py)等set方法对矩阵中相应的数值进行直接操作,但是这里有一个问题:
以倍数为例
**setScale( float sx, float sy, float px, float py)方法指定的中心并不精确,而postScale放大的倍数并不精确,**postScale只能在原有基础上放大指定的倍数,如果需要实现动画效果,则需要指定一个与1接近的参数不停的执行后乘方法,直到超过目标倍数,这个结果最后也不精确,所以我想到了属性动画中的数值发生器 ,这样既实现了动画效果,也保证了结果的精确,比较难的就是需要计算最终状态的倍数。最终的位移值也需要计算,从而实现以指定的点为中心放大图片。
以下为代码:(主要就是计算过程以及各种状态的判断比较复杂)
public class ZoomImageView extends ImageView implements ViewTreeObserver.OnGlobalLayoutListener, ScaleGestureDetector.OnScaleGestureListener, OnTouchListener { /** * 图片处理 */ private Matrix mMatrix; /** * 多点触控 */ private ScaleGestureDetector mScaleGestureDetector; /** * 按下时X坐标 */ private float mDownX; /** * 按下时Y坐标 */ private float mDownY; /** * matrix数组 */ private float[] mFloats = new float[9]; /** * 双击手势控制 */ private GestureDetector mGestureDetector; /** * 存储各个状态下的缩放值 */ private HashMap<Integer, Float> mScaleMap; /** * 记录在最先状态时的坐标,即matrix数组中的2,5项 */ private HashMap<Integer, Float[]> mMinPoint; /** * 是否双击 */ private boolean isDoubleTap; /** * 是否在拖动 */ private boolean isDraging; /** * 是否在自动变化 */ private boolean isAutoChange; /** * 缩放状态 */ public static final int STATUS_SCALE = 1; /** * 移动状态 */ public static final int STATUS_MOVE = 2; /** * 记录当前图片状态 */ private int USER_OPERTION_STATUS = STATUS_SCALE; /** * 默认状态,即图片显示的初始状态 */ public static final int IMAGE_STATUS_DEFAULT = 0; /** * 保证屏幕被完全覆盖 */ public static final int IMAGE_STATUS_FIT_WINDOW = 1; /** * 最大状态,如果放大默认倍数后仍然不能适应窗口,则将显示适应窗口状态 */ public static final int IMAGE_STATUS_MAX = 2; /** * 最小状态,如果初始状态就是缩小后的图片则与默认状态相同,否则scale值为1时为最小状态 */ public static final int IMAGE_STATUS_MIN = 3; /** * 图片当前的状态 */ private int IMAGE_STATUS = IMAGE_STATUS_DEFAULT; private boolean mOnce = false; public ZoomImageView(Context context) { this(context, null); } public ZoomImageView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ZoomImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } public void init() { setScaleType(ScaleType.MATRIX); mMatrix = new Matrix(); mScaleGestureDetector = new ScaleGestureDetector(getContext(), this); setOnTouchListener(this); mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() { @Override public boolean onDoubleTap(MotionEvent e) { isDoubleTap = true; switch (IMAGE_STATUS) { case IMAGE_STATUS_DEFAULT: if (mScaleMap.get(IMAGE_STATUS_DEFAULT).equals(mScaleMap.get(IMAGE_STATUS_FIT_WINDOW))) { setScale(getScale(), mScaleMap.get(IMAGE_STATUS_MAX)); IMAGE_STATUS = IMAGE_STATUS_MAX; } else { setScaleToFitWindow(getScale(), mScaleMap.get(IMAGE_STATUS_FIT_WINDOW), e.getX(), e.getY()); IMAGE_STATUS = IMAGE_STATUS_FIT_WINDOW; } break; case IMAGE_STATUS_FIT_WINDOW: if (mScaleMap.get(IMAGE_STATUS_FIT_WINDOW).equals(mScaleMap.get(IMAGE_STATUS_MAX))) { setScale(getScale(), mScaleMap.get(IMAGE_STATUS_MIN)); IMAGE_STATUS = IMAGE_STATUS_MIN; } else { setScaleToMax(e.getX(), e.getY()); IMAGE_STATUS = IMAGE_STATUS_MAX; } break; case IMAGE_STATUS_MAX: if (mScaleMap.get(IMAGE_STATUS_MIN).equals(mScaleMap.get(IMAGE_STATUS_DEFAULT))) { setScale(getScale(), mScaleMap.get(IMAGE_STATUS_DEFAULT)); IMAGE_STATUS = IMAGE_STATUS_DEFAULT; } else { setScale(getScale(), mScaleMap.get(IMAGE_STATUS_MIN)); IMAGE_STATUS = IMAGE_STATUS_MIN; } break; case IMAGE_STATUS_MIN: setScale(getScale(), mScaleMap.get(IMAGE_STATUS_DEFAULT)); IMAGE_STATUS = IMAGE_STATUS_DEFAULT; break; } if (mScaleMap.get(IMAGE_STATUS) * getDrawable().getMinimumHeight() <= getHeight() && mScaleMap.get(IMAGE_STATUS) * getDrawable().getMinimumWidth() <= getWidth()) { moveToMiddle(); } isDoubleTap = false; return true; } }); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN) @Override public void onGlobalLayout() { if (getDrawable() == null) { return; } if (!mOnce) { Drawable drawable = getDrawable(); if (drawable == null) { return; } int drawableW = drawable.getMinimumWidth(); int drawableH = drawable.getMinimumHeight(); float viewH = getHeight(); float viewW = getWidth(); float scale = Math.min(viewW / drawableW, viewH / drawableH); mMatrix.postTranslate(viewW / 2 - drawableW / 2, viewH / 2 - drawableH / 2); mMatrix.postScale(scale, scale, getWidth() / 2, getHeight() / 2); setImageMatrix(mMatrix); putScaleMapAndPoint(); mOnce = true; } } /** * 当控件挂载到窗口时为其添加OnGlobalLayoutListener接口,并将图片设置到默认状态 */ @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); getViewTreeObserver().addOnGlobalLayoutListener(this); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN) @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); getViewTreeObserver().removeOnGlobalLayoutListener(this); } /** * 存储各个状态下的缩放值和最小值的Matrix.MTRANS_X和Matrix.MTRANS_Y值 */ private void putScaleMapAndPoint() { mScaleMap = new HashMap<>(); mScaleMap.put(IMAGE_STATUS_DEFAULT, getScale()); mScaleMap.put(IMAGE_STATUS_MIN, Math.min(1, getScale())); if (getDrawableHeight() == getHeight() && getDrawableWidth() == getWidth()) { mScaleMap.put(IMAGE_STATUS_FIT_WINDOW, getScale()); } else if (getDrawableHeight() == getHeight()) { mScaleMap.put(IMAGE_STATUS_FIT_WINDOW, getScale() * getWidth() / getDrawableWidth()); } else if (getDrawableWidth() == getWidth()) { mScaleMap.put(IMAGE_STATUS_FIT_WINDOW, getScale() * getHeight() / getDrawableHeight()); } if (mScaleMap.get(IMAGE_STATUS_FIT_WINDOW) >= 3) { mScaleMap.put(IMAGE_STATUS_MAX, mScaleMap.get(IMAGE_STATUS_FIT_WINDOW)); } else { mScaleMap.put(IMAGE_STATUS_MAX, 3.0f); } mMatrix.getValues(mFloats); mMinPoint = new HashMap<>(); float x = ((float) getWidth() - (float) getDrawable().getMinimumWidth() * mScaleMap.get(IMAGE_STATUS_MIN)) / 2; float y = ((float) getHeight() - (float) getDrawable().getMinimumHeight() * mScaleMap.get(IMAGE_STATUS_MIN)) / 2; mMinPoint.put(IMAGE_STATUS_MIN, new Float[]{x, y}); } @Override public boolean onTouch(View v, MotionEvent event) { if (getDrawable() == null) { return true; } if (isAutoChange) { return true; } if (mGestureDetector.onTouchEvent(event)) { return true; } isDoubleTap = false; if (event.getPointerCount() == 2) { USER_OPERTION_STATUS = STATUS_SCALE; } mScaleGestureDetector.onTouchEvent(event); mMatrix.getValues(mFloats); if (USER_OPERTION_STATUS == STATUS_SCALE && event.getPointerCount() == 1 && event.getAction() == MotionEvent.ACTION_UP) { USER_OPERTION_STATUS = STATUS_MOVE; } if (USER_OPERTION_STATUS == STATUS_SCALE) { return true; } switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mDownX = event.getRawX(); mDownY = event.getRawY(); break; case MotionEvent.ACTION_MOVE: if (getScale() <= mScaleMap.get(IMAGE_STATUS_DEFAULT)) { break; } if (Math.abs(event.getRawX() - mDownX) > 18 || Math.abs(event.getRawY() - mDownY) > 18 || isDraging) { isDraging = true; mFloats[Matrix.MTRANS_X] = event.getRawX() - mDownX + mFloats[Matrix.MTRANS_X]; mFloats[Matrix.MTRANS_Y] = event.getRawY() - mDownY + mFloats[Matrix.MTRANS_Y]; mDownY = event.getRawY(); mDownX = event.getRawX(); mMatrix.setValues(mFloats); setImageMatrix(mMatrix); } break; case MotionEvent.ACTION_UP: if (getScale() <= mScaleMap.get(IMAGE_STATUS_DEFAULT)) { break; } if (isDraging) { isDraging = false; checkBorder(); } break; } return true; } @Override public boolean onScaleBegin(ScaleGestureDetector detector) { return true; } @Override public boolean onScale(ScaleGestureDetector detector) { float scaleFactor = detector.getScaleFactor(); mMatrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY()); if (getDrawable() == null) { return true; } setImageMatrix(mMatrix); return true; } @Override public void onScaleEnd(ScaleGestureDetector detector) { if (getScale() < mScaleMap.get(IMAGE_STATUS_DEFAULT)) { IMAGE_STATUS = IMAGE_STATUS_MIN; } if (getScale() > mScaleMap.get(IMAGE_STATUS_DEFAULT) && getScale() < mScaleMap.get(IMAGE_STATUS_FIT_WINDOW)) { IMAGE_STATUS = IMAGE_STATUS_DEFAULT; } if (getScale() > mScaleMap.get(IMAGE_STATUS_MAX)) { IMAGE_STATUS = IMAGE_STATUS_MAX; setScaleToMax(detector.getPreviousSpanX(), detector.getPreviousSpanY()); } if (getScale() < mScaleMap.get(IMAGE_STATUS_MIN)) { IMAGE_STATUS = IMAGE_STATUS_MIN; setScale(getScale(), mScaleMap.get(IMAGE_STATUS_MIN)); } checkBorder(); } /** * 移动到到屏幕合适位置,保证没有空白 */ private void moveToFitView() { if (!isDoubleTap) { mMatrix.getValues(mFloats); if (mFloats[Matrix.MTRANS_X] > 0) { setPost(mFloats[Matrix.MTRANS_X], 0, Matrix.MTRANS_X); } if (mFloats[Matrix.MTRANS_X] + getDrawableWidth() < getWidth()) { setPost(mFloats[Matrix.MTRANS_X] + getDrawableWidth(), getWidth(), Matrix.MTRANS_X, getDrawableWidth()); } if (mFloats[Matrix.MTRANS_Y] > 0) { setPost(mFloats[Matrix.MTRANS_Y], 0, Matrix.MTRANS_Y); } if (mFloats[Matrix.MTRANS_Y] + getDrawableHeight() < getHeight()) { setPost(mFloats[Matrix.MTRANS_Y] + getDrawableHeight(), getHeight(), Matrix.MTRANS_Y, getDrawableHeight()); } } } /** * 移动图片到垂直方向的中间位置 */ private void moveToVerticalMiddle() { if (!isDoubleTap) { mMatrix.getValues(mFloats); setPost(mFloats[Matrix.MTRANS_Y], getHeight() / 2 - getDrawableHeight() / 2, Matrix.MTRANS_Y); if (mFloats[Matrix.MTRANS_X] > 0) { setPost(mFloats[Matrix.MTRANS_X], 0, Matrix.MTRANS_X); } if (mFloats[Matrix.MTRANS_X] + getDrawableWidth() < getWidth()) { setPost(mFloats[Matrix.MTRANS_X] + getDrawableWidth(), getWidth(), Matrix.MTRANS_X, getDrawableWidth()); } } } /** * 移动图片到水平位置的中间位置 */ private void moveToHorizontalMiddle() { mMatrix.getValues(mF f601 loats); if (!isDoubleTap) { setPost(mFloats[Matrix.MTRANS_X], getWidth() / 2 - getDrawableWidth() / 2, Matrix.MTRANS_X); if (mFloats[Matrix.MTRANS_Y] > 0) { setPost(mFloats[Matrix.MTRANS_Y], 0, Matrix.MTRANS_Y); } if (mFloats[Matrix.MTRANS_Y] + getDrawableHeight() < getHeight()) { setPost(mFloats[Matrix.MTRANS_Y] + getDrawableHeight(), getHeight(), Matrix.MTRANS_Y, getDrawableHeight()); } } } /** * 移动图片到屏幕的中间 */ private void moveToMiddle() { mMatrix.getValues(mFloats); if (isDoubleTap || getScale() < mScaleMap.get(IMAGE_STATUS_MIN)) { if (IMAGE_STATUS == IMAGE_STATUS_MIN || ((mScaleMap.get(IMAGE_STATUS_MIN).equals(mScaleMap.get(IMAGE_STATUS_DEFAULT)) && IMAGE_STATUS == IMAGE_STATUS_DEFAULT))) { setPost(mFloats[Matrix.MTRANS_Y], mMinPoint.get(IMAGE_STATUS_MIN)[1], Matrix.MTRANS_Y); setPost(mFloats[Matrix.MTRANS_X], mMinPoint.get(IMAGE_STATUS_MIN)[0], Matrix.MTRANS_X); } else { setPost(mFloats[Matrix.MTRANS_X], mMinPoint.get(IMAGE_STATUS_MIN)[0] - ((getDrawable().getMinimumWidth() * mScaleMap.get(IMAGE_STATUS_MIN)) * (mScaleMap.get(IMAGE_STATUS_DEFAULT) - 1)) / 2, Matrix.MTRANS_X); setPost(mFloats[Matrix.MTRANS_Y], mMinPoint.get(IMAGE_STATUS_MIN)[1] - ((getDrawable().getMinimumHeight() * mScaleMap.get(IMAGE_STATUS_MIN)) * (mScaleMap.get(IMAGE_STATUS_DEFAULT) - 1)) / 2, Matrix.MTRANS_Y); } } else { setPost(mFloats[Matrix.MTRANS_Y], getHeight() / 2 - getDrawableHeight() / 2, Matrix.MTRANS_Y); setPost(mFloats[Matrix.MTRANS_X], getWidth() / 2 - getDrawableWidth() / 2, Matrix.MTRANS_X); } } /** * 数值生成器来控制图片变化 * * @param startValue 初始值,也就是图片缩放后的值 * @param endValue 最终值 * @param matrix matrix数组需要改变的数组位置 */ private void setPost(float startValue, float endValue, final int matrix) { setPost(startValue, endValue, matrix, 0); } /** * 数值生成器来控制图片变化 * * @param startValue 初始值,也就是图片缩放后的值 * @param endValue 最终值 * @param matrix matrix数组需要改变的数组位置 * @param x 对生成的数值的修正值 */ private void setPost(final float startValue, final float endValue, final int matrix, final float x) { ValueAnimator yValueAnimator = ValueAnimator.ofFloat(startValue, endValue); yValueAnimator.setDuration(200).start(); yValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mMatrix.getValues(mFloats); mFloats[matrix] = (Float) animation.getAnimatedValue() - x; isAutoChange = !((Float) animation.getAnimatedValue() == endValue); mMatrix.setValues(mFloats); setImageMatrix(mMatrix); } }); yValueAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationCancel(Animator animation) { isAutoChange = false; } @Override public void onAnimationEnd(Animator animation) { isAutoChange = false; } }); } /** * 数值生成器来控制图片变化 * * @param startValue 初始值,也就是图片缩放后的值 * @param endValue 最终值 */ private void setScale(final float startValue, final float endValue) { ValueAnimator yValueAnimator = ValueAnimator.ofFloat(startValue, endValue); yValueAnimator.setDuration(200).start(); yValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mMatrix.getValues(mFloats); mFloats[Matrix.MSCALE_X] = (float) animation.getAnimatedValue(); mFloats[Matrix.MSCALE_Y] = (float) animation.getAnimatedValue(); isAutoChange = !((float) animation.getAnimatedValue() == endValue); mMatrix.setValues(mFloats); setImageMatrix(mMatrix); } }); yValueAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationCancel(Animator animation) { isAutoChange = false; } @Override public void onAnimationEnd(Animator animation) { isAutoChange = false; } }); } /** * 缩放到保证覆盖屏幕 * * @param startValue 初始值,目前图片缩放的值 * @param endValue 缩放后的值 * @param x 点击事件的X坐标 * @param y 点击事件的Y坐标 */ private void setScaleToFitWindow(final float startValue, final float endValue, final float x, final float y) { mMatrix.getValues(mFloats); final float tranX = mFloats[2]; final float tranY = mFloats[5]; final float drawableH = mScaleMap.get(IMAGE_STATUS_DEFAULT) * getDrawable().getMinimumHeight(); final float drawableW = mScaleMap.get(IMAGE_STATUS_DEFAULT) * getDrawable().getMinimumWidth(); final ValueAnimator yValueAnimator = ValueAnimator.ofFloat(startValue, endValue); yValueAnimator.setDuration(200).start(); yValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mMatrix.getValues(mFloats); mFloats[Matrix.MSCALE_X] = (float) animation.getAnimatedValue(); mFloats[Matrix.MSCALE_Y] = (float) animation.getAnimatedValue(); float a = ((float) animation.getAnimatedValue() - startValue) / (endValue - startValue); if (drawableH == getHeight()) { mFloats[2] = tranX - tranX * a; float end = -(mScaleMap.get(IMAGE_STATUS_FIT_WINDOW) / mScaleMap.get(IMAGE_STATUS_DEFAULT) - 1) * y; mFloats[5] = (end - tranY) * a + tranY; } else if (drawableW == getWidth()) { mFloats[5] = tranY - tranY * a; float end = -(mScaleMap.get(IMAGE_STATUS_FIT_WINDOW) / mScaleMap.get(IMAGE_STATUS_DEFAULT) - 1) * x; mFloats[2] = (end - tranX) * a + tranX; } isAutoChange = !((float) animation.getAnimatedValue() == endValue); mMatrix.setValues(mFloats); setImageMatrix(mMatrix); } }); yValueAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationCancel(Animator animation) { isAutoChange = false; } @Override public void onAnimationEnd(Animator animation) { isAutoChange = false; } }); } /** * 缩放到MAX值 * * @param x 点击事件的X坐标 * @param y 点击事件的Y坐标 */ private void setScaleToMax(final float x, final float y) { final float startValue = getScale(); final float endValue = mScaleMap.get(IMAGE_STATUS_MAX); mMatrix.getValues(mFloats); final float tranX = mFloats[2]; final float tranY = mFloats[5]; final float scale = mScaleMap.get(IMAGE_STATUS_MAX) / getScale(); final ValueAnimator yValueAnimator = ValueAnimator.ofFloat(startValue, endValue); yValueAnimator.setDuration(200).start(); yValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mMatrix.getValues(mFloats); mFloats[Matrix.MSCALE_X] = (float) animation.getAnimatedValue(); mFloats[Matrix.MSCALE_Y] = (float) animation.getAnimatedValue(); Log.e(mFloats[2] + "", mFloats[5] + ""); float a = ((float) animation.getAnimatedValue() - startValue) / (endValue - startValue); float endX = (x - (x - tranX) * scale); float endY = (y - (y - tranY) * scale); mFloats[2] = (endX - tranX) * a + tranX; mFloats[5] = (endY - tranY) * a + tranY; isAutoChange = !((float) animation.getAnimatedValue() == endValue); mMatrix.setValues(mFloats); setImageMatrix(mMatrix); } }); yValueAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationCancel(Animator animation) { isAutoChange = false; } @Override public void onAnimationEnd(Animator animation) { isAutoChange = false; checkBorder(); } }); } /** * 检查边界是否可以覆盖保证不留有空白 */ private void checkBorder() { if (getDrawableHeight() <= getHeight() && getDrawableWidth() <= getWidth()) { moveToMiddle(); } else if (getDrawableHeight() > getHeight() && getDrawableWidth() <= getWidth()) { moveToHorizontalMiddle(); } else if (getDrawableWidth() > getWidth() && getDrawableHeight() <= getHeight()) { moveToVerticalMiddle(); } else if (getDrawableHeight() > getHeight() && getDrawableWidth() > getWidth()) { moveToFitView(); } } /** * @return 获取当前缩放值 */ private float getScale() { float[] floats = new float[9]; mMatrix.getValues(floats); return floats[Matrix.MSCALE_X]; } /** * @return 缩放后图片的宽度 */ private float getDrawableWidth() { return getScale() * getDrawable().getMinimumWidth(); } /** * @return 缩放后图片的高度 */ private float getDrawableHeight() { return getScale() * getDrawable().getMinimumHeight(); } @Override public void setImageDrawable(Drawable drawable) { super.setImageDrawable(drawable); onAttachedToWindow(); init(); } }
相关文章推荐
- 按右键另存图片只能存BMP
- photoshop去除图片上的水印
- upload上传单张图片
- 图片引发的溢出危机(图)
- C#实现把彩色图片灰度化代码分享
- C#将图片和字节流互相转换并显示到页面上
- C#监控文件夹并自动给图片文件打水印的方法
- 纯CSS实现的当鼠标移上图片添加阴影效果代码
- C#实现打开画图的同时载入图片、最大化显示画图窗体的方法
- 随鼠标移动的图片或文字特效代码
- CSS 图片横向排列实现代码
- C#实现将Email地址转成图片显示的方法
- 超级经典一套鼠标控制左右滚动图片带自动翻滚
- 用css实现图片垂直居中的使用技巧
- C++实现读取图片长度和宽度
- C#使用Matrix执行缩放的方法
- PHP根据图片色界在不同位置加水印的方法
- php通过修改header强制图片下载的方法
- PHP安全上传图片的方法
- 使用GD库生成带阴影文字的图片