Browse Source

feat: 融合动画遮罩读取

hexleo 5 years ago
parent
commit
2ac977d6fd

+ 43 - 13
Android/PlayerProj/animtool/src/main/java/com/tencent/qgame/playerproj/animtool/AnimTool.java

@@ -15,6 +15,10 @@
  */
  */
 package com.tencent.qgame.playerproj.animtool;
 package com.tencent.qgame.playerproj.animtool;
 
 
+import com.tencent.qgame.playerproj.animtool.vapx.FrameSet;
+import com.tencent.qgame.playerproj.animtool.vapx.GetMaskFrame;
+import com.tencent.qgame.playerproj.animtool.vapx.SrcSet;
+
 import javax.imageio.ImageIO;
 import javax.imageio.ImageIO;
 import java.awt.image.BufferedImage;
 import java.awt.image.BufferedImage;
 import java.io.BufferedReader;
 import java.io.BufferedReader;
@@ -42,6 +46,7 @@ public class AnimTool {
     private volatile int finishThreadCount = 0;
     private volatile int finishThreadCount = 0;
     private long time;
     private long time;
     private GetAlphaFrame getAlphaFrame = new GetAlphaFrame();
     private GetAlphaFrame getAlphaFrame = new GetAlphaFrame();
+    private GetMaskFrame getMaskFrame = new GetMaskFrame();
     private IToolListener toolListener;
     private IToolListener toolListener;
 
 
     public void setToolListener(IToolListener toolListener) {
     public void setToolListener(IToolListener toolListener) {
@@ -57,7 +62,7 @@ public class AnimTool {
         createAllFrameImage(commonArg, new Runnable() {
         createAllFrameImage(commonArg, new Runnable() {
             @Override
             @Override
             public void run() {
             public void run() {
-                if (needVideo) {
+                if (finalCheck(commonArg) && needVideo) {
                     // 最终生成视频文件
                     // 最终生成视频文件
                     createVideo(commonArg);
                     createVideo(commonArg);
                 }
                 }
@@ -74,6 +79,22 @@ public class AnimTool {
         return CommonArgTool.autoFillAndCheck(commonArg);
         return CommonArgTool.autoFillAndCheck(commonArg);
     }
     }
 
 
+    private boolean finalCheck(CommonArg commonArg) {
+        if (commonArg.isVapx) {
+            if (commonArg.srcSet.srcs.isEmpty()) {
+                TLog.i(TAG, "vapx error: src is empty");
+                return false;
+            }
+            for (SrcSet.Src src : commonArg.srcSet.srcs) {
+                if (src.w <=0 || src.h <= 0) {
+                    TLog.i(TAG, "vapx error: src.w=" + src.w + ",src.h=" + src.h);
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
     private void createAllFrameImage(final CommonArg commonArg, final Runnable finishRunnable) throws Exception{
     private void createAllFrameImage(final CommonArg commonArg, final Runnable finishRunnable) throws Exception{
         if (!checkCommonArg(commonArg)) {
         if (!checkCommonArg(commonArg)) {
             if (toolListener != null) toolListener.onError();
             if (toolListener != null) toolListener.onError();
@@ -143,6 +164,12 @@ public class AnimTool {
     private void createFrame(CommonArg commonArg, int frameIndex) throws Exception {
     private void createFrame(CommonArg commonArg, int frameIndex) throws Exception {
         File inputFile = new File(commonArg.inputPath + String.format("%03d", frameIndex)+".png");
         File inputFile = new File(commonArg.inputPath + String.format("%03d", frameIndex)+".png");
         GetAlphaFrame.AlphaFrameOut videoFrame = getAlphaFrame.createFrame(commonArg, inputFile);
         GetAlphaFrame.AlphaFrameOut videoFrame = getAlphaFrame.createFrame(commonArg, inputFile);
+        if (commonArg.isVapx) {
+            FrameSet.FrameObj frameObj = getMaskFrame.getFrameObj(frameIndex, commonArg, videoFrame.argb);
+            if (frameObj != null) {
+                commonArg.frameSet.frameObjs.add(frameObj);
+            }
+        }
         if (videoFrame == null) {
         if (videoFrame == null) {
             TLog.i(TAG, "frameIndex="+frameIndex +" is empty");
             TLog.i(TAG, "frameIndex="+frameIndex +" is empty");
             return;
             return;
@@ -202,17 +229,20 @@ public class AnimTool {
      * @param commonArg
      * @param commonArg
      */
      */
     private void createVapcJson(CommonArg commonArg) {
     private void createVapcJson(CommonArg commonArg) {
-        String json = "\"info\":{\"v\":$(v),\"f\":$(f),\"w\":$(w),\"h\":$(h),\"videoW\":$(videoW),\"videoH\":$(videoH),\"orien\":0,\"fps\":$(fps),\"isVapx\":0,\"aFrame\":$(aFrame),\"rgbFrame\":$(rgbFrame)}";
-        json = json.replace("$(v)", String.valueOf(commonArg.version));
-        json = json.replace("$(f)", String.valueOf(commonArg.totalFrame));
-        json = json.replace("$(w)", String.valueOf(commonArg.rgbPoint.w));
-        json = json.replace("$(h)", String.valueOf(commonArg.rgbPoint.h));
-        json = json.replace("$(fps)", String.valueOf(commonArg.fps));
-        json = json.replace("$(videoW)", String.valueOf(commonArg.outputW));
-        json = json.replace("$(videoH)", String.valueOf(commonArg.outputH));
-        json = json.replace("$(aFrame)", commonArg.alphaPoint.toString());
-        json = json.replace("$(rgbFrame)", commonArg.rgbPoint.toString());
 
 
+        String json = "\"info\":{" +
+                "\"v\":" + commonArg.version + "," +
+                "\"f\":" + commonArg.totalFrame + "," +
+                "\"w\":" + commonArg.rgbPoint.w + "," +
+                "\"h\":" + commonArg.rgbPoint.h + "," +
+                "\"fps\":" + commonArg.fps + "," +
+                "\"videoW\":" + commonArg.outputW + "," +
+                "\"videoH\":" + commonArg.outputH + "," +
+                "\"aFrame\":" + commonArg.alphaPoint.toString() + "," +
+                "\"rgbFrame\":" + commonArg.rgbPoint.toString() + "," +
+                "\"isVapx\":" + (commonArg.isVapx ? 1 : 0) + "," +
+                "\"orien\":" + 0 +
+                "}";
         TLog.i(TAG, "{" + json + "}");
         TLog.i(TAG, "{" + json + "}");
 
 
         StringBuilder sb = new StringBuilder();
         StringBuilder sb = new StringBuilder();
@@ -221,6 +251,8 @@ public class AnimTool {
         if (commonArg.isVapx) {
         if (commonArg.isVapx) {
             sb.append(",");
             sb.append(",");
             sb.append(commonArg.srcSet.toString());
             sb.append(commonArg.srcSet.toString());
+            sb.append(",");
+            sb.append(commonArg.frameSet.toString());
         }
         }
         sb.append("}");
         sb.append("}");
         json = sb.toString();
         json = sb.toString();
@@ -234,8 +266,6 @@ public class AnimTool {
             e.printStackTrace();
             e.printStackTrace();
             throw new RuntimeException();
             throw new RuntimeException();
         }
         }
-
-
     }
     }
 
 
 
 

+ 2 - 0
Android/PlayerProj/animtool/src/main/java/com/tencent/qgame/playerproj/animtool/CommonArg.java

@@ -50,6 +50,8 @@ public class CommonArg {
 
 
     public PointRect alphaPoint = new PointRect();  // alpha 区域
     public PointRect alphaPoint = new PointRect();  // alpha 区域
 
 
+    public boolean isVLayout = false; // 是否为垂直布局
+
     public int outputW = 0; // 输出最终视频的宽高
     public int outputW = 0; // 输出最终视频的宽高
 
 
     public int outputH = 0;
     public int outputH = 0;

+ 23 - 1
Android/PlayerProj/animtool/src/main/java/com/tencent/qgame/playerproj/animtool/CommonArgTool.java

@@ -1,5 +1,7 @@
 package com.tencent.qgame.playerproj.animtool;
 package com.tencent.qgame.playerproj.animtool;
 
 
+import com.tencent.qgame.playerproj.animtool.vapx.SrcSet;
+
 import java.awt.image.BufferedImage;
 import java.awt.image.BufferedImage;
 import java.io.File;
 import java.io.File;
 
 
@@ -43,10 +45,28 @@ class CommonArgTool {
         // 帧图片生成路径
         // 帧图片生成路径
         commonArg.frameOutputPath = commonArg.outputPath + AnimTool.FRAME_IMAGE_DIR;
         commonArg.frameOutputPath = commonArg.outputPath + AnimTool.FRAME_IMAGE_DIR;
 
 
+        // srcId自动生成 & 融合动画路径检查 & z序
+        if (commonArg.isVapx) {
+            int size = commonArg.srcSet.srcs.size();
+            SrcSet.Src src;
+            for (int i=0; i<size; i++) {
+                src = commonArg.srcSet.srcs.get(i);
+                src.srcId = String.valueOf(i);
+                src.z = i;
+                File srcPath = new File(src.srcPath);
+                if (!srcPath.exists()) {
+                    TLog.i(TAG, "error: src="+ src.srcId+",path invalid " + src.srcPath);
+                    continue;
+                }
+                if (!File.separator.equals(src.srcPath.substring(src.srcPath.length() - 1))) {
+                    src.srcPath = src.srcPath + File.separator;
+                }
+            }
+        }
 
 
         // 限定scale的值
         // 限定scale的值
         if (commonArg.scale < 0.5f) {
         if (commonArg.scale < 0.5f) {
-            commonArg.scale =0.5f;
+            commonArg.scale = 0.5f;
         }
         }
 
 
         if (commonArg.scale > 1f) {
         if (commonArg.scale > 1f) {
@@ -85,12 +105,14 @@ class CommonArgTool {
         int vMaxLen = Math.max(vW, vH);
         int vMaxLen = Math.max(vW, vH);
 
 
         if (hMaxLen > vMaxLen) { // 竖直布局
         if (hMaxLen > vMaxLen) { // 竖直布局
+            commonArg.isVLayout = true;
             commonArg.alphaPoint.x = 0;
             commonArg.alphaPoint.x = 0;
             commonArg.alphaPoint.y = commonArg.rgbPoint.h + commonArg.gap;
             commonArg.alphaPoint.y = commonArg.rgbPoint.h + commonArg.gap;
 
 
             commonArg.outputW = commonArg.rgbPoint.w;
             commonArg.outputW = commonArg.rgbPoint.w;
             commonArg.outputH = commonArg.rgbPoint.h + commonArg.gap + commonArg.alphaPoint.h;
             commonArg.outputH = commonArg.rgbPoint.h + commonArg.gap + commonArg.alphaPoint.h;
         } else { // 水平布局
         } else { // 水平布局
+            commonArg.isVLayout = false;
             commonArg.alphaPoint.x = commonArg.rgbPoint.w + commonArg.gap;
             commonArg.alphaPoint.x = commonArg.rgbPoint.w + commonArg.gap;
             commonArg.alphaPoint.y = 0;
             commonArg.alphaPoint.y = 0;
 
 

+ 10 - 8
Android/PlayerProj/animtool/src/main/java/com/tencent/qgame/playerproj/animtool/Main.java

@@ -62,11 +62,11 @@ public class Main {
         commonArg.mp4editCmd = "mp4edit";
         commonArg.mp4editCmd = "mp4edit";
 
 
         /*
         /*
-         * 是否开启h265(默认关闭
+         * 是否开启h265(默认开启
          * 优点:压缩率更高,视频更清晰
          * 优点:压缩率更高,视频更清晰
          * 缺点:Android 4.x系统 & 极少部分低端机 无法播放265视频
          * 缺点:Android 4.x系统 & 极少部分低端机 无法播放265视频
          */
          */
-        commonArg.enableH265 = false;
+        commonArg.enableH265 = true;
         // fps
         // fps
         commonArg.fps = 24;
         commonArg.fps = 24;
         // 素材文件路径
         // 素材文件路径
@@ -90,19 +90,21 @@ public class Main {
 
 
         String path = "/Users/hexleo/temp/moon/Demo/";
         String path = "/Users/hexleo/temp/moon/Demo/";
         /*
         /*
-         * 是否开启h265(默认关闭
+         * 是否开启h265(默认开启
          * 优点:压缩率更高,视频更清晰
          * 优点:压缩率更高,视频更清晰
          * 缺点:Android 4.x系统 & 极少部分低端机 无法播放265视频
          * 缺点:Android 4.x系统 & 极少部分低端机 无法播放265视频
          */
          */
-        commonArg.enableH265 = false;
+        commonArg.enableH265 = true;
         // fps
         // fps
         commonArg.fps = 24;
         commonArg.fps = 24;
         // 素材文件路径
         // 素材文件路径
         commonArg.inputPath = path + "video";
         commonArg.inputPath = path + "video";
-        // alpha 区域缩放大小  (0.5 - 1)
-        commonArg.scale = 0.5f;
         // 启动融合动画
         // 启动融合动画
         commonArg.isVapx = true;
         commonArg.isVapx = true;
+        if (commonArg.isVapx) {
+            // 融合动画默认需要缩放0.5f 空出区域
+            commonArg.scale = 0.5f;
+        }
         // src 设置
         // src 设置
         commonArg.srcSet = getSrcSet(path);
         commonArg.srcSet = getSrcSet(path);
 
 
@@ -142,7 +144,7 @@ public class Main {
             SrcSet.Src src = new SrcSet.Src();
             SrcSet.Src src = new SrcSet.Src();
             src.srcPath = path + "mask3";
             src.srcPath = path + "mask3";
             src.srcId = "3";
             src.srcId = "3";
-            src.srcType = SrcSet.Src.SRC_TYPE_IMG;
+            src.srcType = SrcSet.Src.SRC_TYPE_TXT;
             src.srcTag = "text1";
             src.srcTag = "text1";
             src.fitType = SrcSet.Src.FIT_TYPE_FITXY;
             src.fitType = SrcSet.Src.FIT_TYPE_FITXY;
             src.color = "#0000ff";
             src.color = "#0000ff";
@@ -154,7 +156,7 @@ public class Main {
             SrcSet.Src src = new SrcSet.Src();
             SrcSet.Src src = new SrcSet.Src();
             src.srcPath = path + "mask4";
             src.srcPath = path + "mask4";
             src.srcId = "4";
             src.srcId = "4";
-            src.srcType = SrcSet.Src.SRC_TYPE_IMG;
+            src.srcType = SrcSet.Src.SRC_TYPE_TXT;
             src.srcTag = "text2";
             src.srcTag = "text2";
             src.fitType = SrcSet.Src.FIT_TYPE_FITXY;
             src.fitType = SrcSet.Src.FIT_TYPE_FITXY;
             src.color = "#00ff00";
             src.color = "#00ff00";

+ 3 - 2
Android/PlayerProj/animtool/src/main/java/com/tencent/qgame/playerproj/animtool/vapx/FrameSet.java

@@ -4,11 +4,12 @@ import com.tencent.qgame.playerproj.animtool.data.PointRect;
 
 
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.List;
+import java.util.Vector;
 
 
 public class FrameSet {
 public class FrameSet {
 
 
-
-    public List<FrameObj> frameObjs = new ArrayList<>();
+    // for sync
+    public Vector<FrameObj> frameObjs = new Vector<>();
 
 
     @Override
     @Override
     public String toString() {
     public String toString() {

+ 175 - 0
Android/PlayerProj/animtool/src/main/java/com/tencent/qgame/playerproj/animtool/vapx/GetMaskFrame.java

@@ -0,0 +1,175 @@
+package com.tencent.qgame.playerproj.animtool.vapx;
+
+import com.tencent.qgame.playerproj.animtool.CommonArg;
+import com.tencent.qgame.playerproj.animtool.TLog;
+import com.tencent.qgame.playerproj.animtool.data.PointRect;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+
+import javax.imageio.ImageIO;
+
+/**
+ * 获取融合动画遮罩
+ */
+public class GetMaskFrame {
+
+    private static final String TAG = "GetMaskFrame";
+
+    public FrameSet.FrameObj getFrameObj(int frameIndex, CommonArg commonArg, int[] outputArgb) throws Exception {
+
+        FrameSet.FrameObj frameObj = new FrameSet.FrameObj();
+        frameObj.frameIndex = frameIndex;
+
+        FrameSet.Frame frame;
+        // 需要放置的位置
+        int x;
+        int y;
+        int gap = commonArg.gap;
+        if (commonArg.isVLayout) {
+            x = commonArg.alphaPoint.w + gap;
+            y = commonArg.alphaPoint.y;
+        } else {
+            x = commonArg.alphaPoint.x;
+            y = commonArg.alphaPoint.h + gap;
+        }
+        int startX = x;
+        int lastMaxY = y;
+        for (int i=0; i<commonArg.srcSet.srcs.size(); i++) {
+            frame = getFrame(frameIndex, commonArg.srcSet.srcs.get(i), outputArgb, commonArg.outputW, commonArg.outputH, x, y, startX, lastMaxY);
+            if (frame == null) continue;
+            // 计算下一个遮罩起点
+            x = frame.mFrame.x + frame.mFrame.w + gap;
+            y = frame.mFrame.y;
+            int newY = frame.mFrame.y + frame.mFrame.h + gap;
+            if (newY > lastMaxY) {
+                lastMaxY = newY;
+            }
+
+            frameObj.frames.add(frame);
+        }
+
+        if (frameObj.frames.isEmpty()) {
+            return null;
+        }
+        return frameObj;
+    }
+
+
+    private FrameSet.Frame getFrame(int frameIndex, SrcSet.Src src, int[] outputArgb, int outW, int outH, int x, int y, int startX, int lastMaxY) throws Exception {
+        File inputFile = new File(src.srcPath  + String.format("%03d", frameIndex)+".png");
+        if (!inputFile.exists()) {
+            return null;
+        }
+
+        BufferedImage inputBuf = ImageIO.read(inputFile);
+        int maskW = inputBuf.getWidth();
+        int maskH = inputBuf.getHeight();
+        int[] maskArgb = inputBuf.getRGB(0, 0, maskW, maskH, null, 0, maskW);
+
+        FrameSet.Frame frame = new FrameSet.Frame();
+        frame.srcId = src.srcId;
+        frame.z = src.z;
+
+        frame.frame = getSrcFramePoint(maskArgb, maskW, maskH);
+        if (frame.frame == null) {
+            // 有文件,但内容是空
+            return null;
+        }
+
+        PointRect mFrame = new PointRect(x, y, frame.frame.w, frame.frame.h);
+        // 计算是否能放下遮罩
+        if (mFrame.x + mFrame.w > outW) { // 超宽换行
+            mFrame.x = startX;
+            mFrame.y = lastMaxY;
+            if (mFrame.x + mFrame.w > outW) {
+                TLog.i(TAG, "Error: frameIndex=" + frameIndex + ",src=" + src.srcId + ", no more space for(w)" + mFrame);
+                return null;
+            }
+        }
+        if (mFrame.y + mFrame.h > outH) { // 高度不够直接错误
+            TLog.i(TAG, "Error: frameIndex=" + frameIndex + ",src=" + src.srcId + ", no more space(h)" + mFrame);
+            return null;
+        }
+        frame.mFrame = mFrame;
+
+        fillMaskToOutput(outputArgb, outW, maskArgb, maskW, frame.frame, frame.mFrame, SrcSet.Src.SRC_TYPE_TXT.equals(src.srcType));
+
+        // 设置src的w,h 取所有遮罩里最大值
+        synchronized (GetMaskFrame.class) {
+            // 只按宽度进行判断防止横跳
+            if (frame.frame.w > src.w) {
+                src.w = frame.frame.w;
+                src.h = frame.mFrame.h;
+            }
+        }
+        return frame;
+    }
+
+
+    /**
+     * 获取遮罩位置信息 并转换为黑白
+     */
+    private PointRect getSrcFramePoint(int[] maskArgb, int w, int h) {
+
+        PointRect point = new PointRect();
+
+        int startX = -1;
+        int startY = -1;
+        int maxX = 0;
+        int maxY = 0;
+        for (int y=0; y<h; y++) {
+            for (int x = 0; x < w; x++) {
+                int alpha = maskArgb[x + y*w] >>> 24;
+                if (alpha > 0) {
+                    if (startX == -1 || startY == -1) {
+                        startX = x;
+                        startY = y;
+                    }
+                    if (x > maxX) maxX = x;
+                    if (y > maxY) maxY = y;
+                }
+            }
+        }
+
+        point.x = startX;
+        point.y = startY;
+        point.w = maxX - startX;
+        point.h = maxY - startY;
+        if (point.x == -1 || point.y == -1 || point.w <=0 || point.h <= 0) return null;
+
+        return point;
+
+    }
+
+
+    private void fillMaskToOutput(int[] outputArgb, int outW,
+                                  int[] maskArgb, int maskW,
+                                  PointRect frame,
+                                  PointRect mFrame,
+                                  boolean isTxtMask) {
+        for (int y=0; y < frame.h; y++) {
+            for (int x=0; x < frame.w; x++) {
+                int maskXOffset = frame.x;
+                int maskYOffset = frame.y;
+                // 先从遮罩 maskArgb 取色
+                int maskColor = maskArgb[x + maskXOffset + (y + maskYOffset) * maskW];
+                int alpha = maskColor >>> 24;
+                // 文字mask 黑色部分不遮挡,红色部分被遮挡
+                if (isTxtMask) {
+                    int maskRed = (maskColor & 0x00ff0000) >>> 16;
+                    alpha = 255 - maskRed; // 红色部分算遮挡
+                }
+                // 最终color
+                int color = 0xff000000 + (alpha << 16) + (alpha << 8) + alpha;
+
+                // 将遮罩颜色放置到视频中对应区域
+                int outputXOffset = mFrame.x;
+                int outputYOffset = mFrame.y;
+                outputArgb[x + outputXOffset + (y + outputYOffset) * outW] = color;
+
+            }
+        }
+    }
+
+}

+ 3 - 3
Android/PlayerProj/animtool/src/main/java/com/tencent/qgame/playerproj/animtool/vapx/SrcSet.java

@@ -28,7 +28,7 @@ public class SrcSet {
          */
          */
         public String srcId = "";
         public String srcId = "";
         public String srcType = SRC_TYPE_IMG;
         public String srcType = SRC_TYPE_IMG;
-        public String loadTyp = LOAD_TYPE_NET;
+        public String loadType = LOAD_TYPE_NET;
         public String srcTag = "";
         public String srcTag = "";
         public String color = "#000000";
         public String color = "#000000";
         public String style = TEXT_STYLE_DEFAULT;
         public String style = TEXT_STYLE_DEFAULT;
@@ -54,9 +54,9 @@ public class SrcSet {
                     json.append("\"color\":").append("\"").append(color.trim()).append("\",");
                     json.append("\"color\":").append("\"").append(color.trim()).append("\",");
                 }
                 }
                 json.append("\"style\":").append("\"").append(style).append("\",");
                 json.append("\"style\":").append("\"").append(style).append("\",");
-                json.append("\"loadTyp\":").append("\"").append(LOAD_TYPE_LOC).append("\",");
+                json.append("\"loadType\":").append("\"").append(LOAD_TYPE_LOC).append("\",");
             } else {
             } else {
-                json.append("\"loadTyp\":").append("\"").append(loadTyp).append("\",");
+                json.append("\"loadType\":").append("\"").append(loadType).append("\",");
             }
             }