卡片翻页算法
1、描述:计算翻开的卡片到平面的投影来模拟卡片打开的效果 。视点位于卡片上边缘中点正上方。
以w、h表示原卡片的宽高,w1、h1表示投影的宽高,H表示视点高度 , a为翻开的卡片与平面上的卡片夹角,有以下公式:
h1 = h*cosa *H /(H-h*sina)
w1 = w*H/(H-h*sina)
投影计算算法(这里仅考虑了第一象限)为:
public ImageBuffer openCard(int[] pixels, int width, int height, double pi) { final int H = width * 3; double sina = Math.sin(pi); double cosa = Math.cos(pi); int newHeight = (int) (height * cosa * H / (H - height * sina)); int newWidth = (int) (width * H / (H - height * sina)); int[] newPixels = new int[newHeight * newWidth]; int length = newPixels.length; for (int i = 0; i < height; i++) { double temp = H / (H - i * sina); int di = (int) (i * cosa * temp); int pre = i * width; int dPre = di * newWidth; int curWidth = (int) (width * temp); int dPos = (newWidth - curWidth) >> 1; for (int j = 0; j < width; j++) { int dj = (int) (j * temp); int index = dPre + dj + dPos; if (index >= length) { index = length - 1; } newPixels[index] = pixels[pre + j]; } } ImageBuffer ib = new ImageBuffer(); ib.height = newHeight; ib.width = newWidth; ib.pixels = newPixels; return ib; } public class ImageBuffer { public int[] pixels; public int width; public int height; public String toString() { return "width : " + width + " height : " + height; } }
2、上述算法会出现一个问题,不能保证卡片投影的每一个点都被设值。例如 如果投影面积大于原卡片面积,上述算法有部分点透明,效果比较查
故改进算法,根据公式给每个投影上的点,找到原图上面的对应的点。公式为:
h = h1 * H / ( H*cosa +h1*sina)
w = w1 * H * cosa /( H* cosa + h1 *sina )
具体算法(这里已经考虑了第一、第二象限)为:
public static ImageBuffer reopenCard(int[] pixels, int width, int height, double pi) { final int H = width * 3; final double PI = 3.14; final double HPI = PI/2; pi = pi%PI; double a = pi>HPI ? PI-pi : pi; double sina = Math.sin(a); double cosa = Math.cos(a); int newHeight = (int) (height * cosa * H / (H - height * sina)); int newWidth = (int) (width * H / (H - height * sina)); int[] newPixels = new int[newHeight * newWidth]; for (int i = 0; i < newHeight; i++) { int h = pi>HPI ?newHeight -i : i; double temph = H * cosa + h * sina; int dh = (int) (h * H / temph); double tempw = H * cosa / temph; int curWidth = (int) (width * H / (H - dh * sina)); int offset = (newWidth - curWidth) >> 1; int end = newWidth - offset; int pre = i * newWidth; int dPre = dh * width; for (int j = offset; j < end; j++) { int dw = (int) ((j - offset) * tempw); newPixels[pre + j] = pixels[dPre + dw]; } } ImageBuffer ib = new ImageBuffer(); ib.height = newHeight; ib.width = newWidth; ib.pixels = newPixels; return ib; }
3、尝试使用NDK,希望能提升效率,但是测试发现效率反而有所下降,猜测可能是像素数组在java类型和c之间转换导致效率耗损:
#define HPI 1.57 JNIEXPORT jobject JNICALL Java_com_xyl_bitmap_filter_FlipBitmap_openCard(JNIEnv *env , jobject jobj , jintArray pixels , jint width , jint height , jdouble pi ) { jdouble PI = 3.14; jint H = width *3; //pi = pi%PI; jdouble a = pi>HPI ? PI-pi : pi; jdouble sina = sin(a); jdouble cosa = cos(a); jint newHeight = (jint) (height * cosa * H / (H - height * sina)); jint newWidth = (jint) (width * H / (H - height * sina)); jint length = width*height; jint newLength = newHeight*newWidth; jint *jpixels ,* newPixels; jpixels = malloc(sizeof(jint)*length); (*env)->GetIntArrayRegion(env , pixels , 0 , length , jpixels); newPixels = malloc(sizeof(jint) *newLength); jint i=0,j=0; for (; i < newHeight; i++) { jint h = pi>HPI ?newHeight -i : i; jdouble temph = H * cosa + h * sina; jint dh = (jint) (h * H / temph); jdouble tempw = H * cosa / temph; jint curWidth = (jint) (width * H / (H - dh * sina)); jint offset = (newWidth - curWidth) >> 1; jint end = newWidth - offset; jint pre = i * newWidth; jint dPre = dh * width; j = offset; for (; j < end; j++) { jint dw = (jint) ((j - offset) * tempw); newPixels[pre + j] = jpixels[dPre + dw]; } } jintArray resultPixels = (*env)->NewIntArray(env , newLength); (*env)->SetIntArrayRegion(env , resultPixels , 0 ,newLength , newPixels); jclass imageBufferClass = (*env)->FindClass(env , "com/xyl/bitmap/filter/ImageBuffer"); if(imageBufferClass == NULL) { return; } jmethodID cid = (*env)->GetMethodID(env , imageBufferClass ,"<init>" , "()V"); if(cid == NULL) { return ; } jobject imageBuffer = (*env)->NewObject(env , imageBufferClass , cid ); jfieldID widthId , heightId , pixelsId; widthId = (*env)->GetFieldID(env ,imageBufferClass,"width" ,"I"); heightId = (*env)->GetFieldID(env ,imageBufferClass,"height" ,"I"); pixelsId = (*env)->GetFieldID(env ,imageBufferClass,"pixels" ,"[I"); if(widthId == NULL || heightId == NULL || pixelsId == NULL) { return; } (*env)->SetIntField(env , imageBuffer , widthId , newWidth); (*env)->SetIntField(env , imageBuffer , heightId , newHeight); (*env)->SetObjectField(env , imageBuffer , pixelsId , resultPixels); free(newPixels); free(jpixels); return imageBuffer; }