1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.filterpacks.imageproc;
18
19import android.filterfw.core.Filter;
20import android.filterfw.core.FilterContext;
21import android.filterfw.core.Frame;
22import android.filterfw.core.FrameFormat;
23import android.filterfw.core.FrameManager;
24import android.filterfw.core.GenerateFieldPort;
25import android.filterfw.core.KeyValueMap;
26import android.filterfw.core.MutableFrameFormat;
27import android.filterfw.core.Program;
28import android.filterfw.core.ShaderProgram;
29import android.filterfw.format.ImageFormat;
30
31import android.util.Log;
32
33public class ImageStitcher extends Filter {
34
35    @GenerateFieldPort(name = "xSlices")
36    private int mXSlices;
37
38    @GenerateFieldPort(name = "ySlices")
39    private int mYSlices;
40
41    @GenerateFieldPort(name = "padSize")
42    private int mPadSize;
43
44    private Program mProgram;
45    private Frame mOutputFrame;
46
47    private int mInputWidth;
48    private int mInputHeight;
49
50    private int mImageWidth;
51    private int mImageHeight;
52
53    private int mSliceWidth;
54    private int mSliceHeight;
55
56    private int mSliceIndex;
57
58    public ImageStitcher(String name) {
59        super(name);
60        mSliceIndex = 0;
61    }
62
63    @Override
64    public void setupPorts() {
65        addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA,
66                                                      FrameFormat.TARGET_GPU));
67        addOutputBasedOnInput("image", "image");
68    }
69
70    @Override
71    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
72        return inputFormat;
73    }
74
75    private FrameFormat calcOutputFormatForInput(FrameFormat format) {
76        MutableFrameFormat outputFormat = format.mutableCopy();
77
78        mInputWidth = format.getWidth();
79        mInputHeight = format.getHeight();
80
81        mSliceWidth = mInputWidth - 2 * mPadSize;
82        mSliceHeight = mInputHeight - 2 * mPadSize;
83
84        mImageWidth =  mSliceWidth * mXSlices;
85        mImageHeight = mSliceHeight * mYSlices;
86
87        outputFormat.setDimensions(mImageWidth, mImageHeight);
88        return outputFormat;
89    }
90
91    @Override
92    public void process(FilterContext context) {
93        // Get input frame
94        Frame input = pullInput("image");
95        FrameFormat format = input.getFormat();
96
97        // Create output frame
98        if (mSliceIndex == 0) {
99            mOutputFrame = context.getFrameManager().newFrame(calcOutputFormatForInput(format));
100        } else {
101            if ((format.getWidth() != mInputWidth) ||
102                (format.getHeight() != mInputHeight)) {
103                // CHECK input format here
104                throw new RuntimeException("Image size should not change.");
105            }
106        }
107
108        // Create the program if not created already
109        if (mProgram == null) {
110            mProgram = ShaderProgram.createIdentity(context);
111        }
112
113        // TODO(rslin) : not sure shifting by 0.5 is needed.
114        float x0 = ((float) mPadSize) / mInputWidth;
115        float y0 = ((float) mPadSize) / mInputHeight;
116
117        int outputOffsetX = (mSliceIndex % mXSlices) * mSliceWidth;
118        int outputOffsetY = (mSliceIndex / mXSlices) * mSliceHeight;
119
120        float outputWidth = (float) Math.min(mSliceWidth, mImageWidth - outputOffsetX);
121        float outputHeight = (float) Math.min(mSliceHeight, mImageHeight - outputOffsetY);
122
123        // We need to set the source rect as well because the input are padded images.
124        ((ShaderProgram) mProgram).setSourceRect(x0, y0,
125                                                 outputWidth / mInputWidth,
126                                                 outputHeight / mInputHeight);
127
128        ((ShaderProgram) mProgram).setTargetRect(((float) outputOffsetX)/ mImageWidth,
129                                                 ((float) outputOffsetY) / mImageHeight,
130                                                 outputWidth / mImageWidth,
131                                                 outputHeight / mImageHeight);
132
133        // Process this tile
134        mProgram.process(input, mOutputFrame);
135        mSliceIndex++;
136
137        // Push output
138        if (mSliceIndex == mXSlices * mYSlices) {
139            pushOutput("image", mOutputFrame);
140            mOutputFrame.release();
141            mSliceIndex = 0;
142        }
143    }
144}
145