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.GenerateFieldPort;
24import android.filterfw.core.MutableFrameFormat;
25import android.filterfw.core.Program;
26import android.filterfw.core.ShaderProgram;
27import android.filterfw.format.ImageFormat;
28
29public class ImageStitcher extends Filter {
30
31    @GenerateFieldPort(name = "xSlices")
32    private int mXSlices;
33
34    @GenerateFieldPort(name = "ySlices")
35    private int mYSlices;
36
37    @GenerateFieldPort(name = "padSize")
38    private int mPadSize;
39
40    private Program mProgram;
41    private Frame mOutputFrame;
42
43    private int mInputWidth;
44    private int mInputHeight;
45
46    private int mImageWidth;
47    private int mImageHeight;
48
49    private int mSliceWidth;
50    private int mSliceHeight;
51
52    private int mSliceIndex;
53
54    public ImageStitcher(String name) {
55        super(name);
56        mSliceIndex = 0;
57    }
58
59    @Override
60    public void setupPorts() {
61        addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA,
62                                                      FrameFormat.TARGET_GPU));
63        addOutputBasedOnInput("image", "image");
64    }
65
66    @Override
67    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
68        return inputFormat;
69    }
70
71    private FrameFormat calcOutputFormatForInput(FrameFormat format) {
72        MutableFrameFormat outputFormat = format.mutableCopy();
73
74        mInputWidth = format.getWidth();
75        mInputHeight = format.getHeight();
76
77        mSliceWidth = mInputWidth - 2 * mPadSize;
78        mSliceHeight = mInputHeight - 2 * mPadSize;
79
80        mImageWidth =  mSliceWidth * mXSlices;
81        mImageHeight = mSliceHeight * mYSlices;
82
83        outputFormat.setDimensions(mImageWidth, mImageHeight);
84        return outputFormat;
85    }
86
87    @Override
88    public void process(FilterContext context) {
89        // Get input frame
90        Frame input = pullInput("image");
91        FrameFormat format = input.getFormat();
92
93        // Create output frame
94        if (mSliceIndex == 0) {
95            mOutputFrame = context.getFrameManager().newFrame(calcOutputFormatForInput(format));
96        } else {
97            if ((format.getWidth() != mInputWidth) ||
98                (format.getHeight() != mInputHeight)) {
99                // CHECK input format here
100                throw new RuntimeException("Image size should not change.");
101            }
102        }
103
104        // Create the program if not created already
105        if (mProgram == null) {
106            mProgram = ShaderProgram.createIdentity(context);
107        }
108
109        // TODO(rslin) : not sure shifting by 0.5 is needed.
110        float x0 = ((float) mPadSize) / mInputWidth;
111        float y0 = ((float) mPadSize) / mInputHeight;
112
113        int outputOffsetX = (mSliceIndex % mXSlices) * mSliceWidth;
114        int outputOffsetY = (mSliceIndex / mXSlices) * mSliceHeight;
115
116        float outputWidth = (float) Math.min(mSliceWidth, mImageWidth - outputOffsetX);
117        float outputHeight = (float) Math.min(mSliceHeight, mImageHeight - outputOffsetY);
118
119        // We need to set the source rect as well because the input are padded images.
120        ((ShaderProgram) mProgram).setSourceRect(x0, y0,
121                                                 outputWidth / mInputWidth,
122                                                 outputHeight / mInputHeight);
123
124        ((ShaderProgram) mProgram).setTargetRect(((float) outputOffsetX)/ mImageWidth,
125                                                 ((float) outputOffsetY) / mImageHeight,
126                                                 outputWidth / mImageWidth,
127                                                 outputHeight / mImageHeight);
128
129        // Process this tile
130        mProgram.process(input, mOutputFrame);
131        mSliceIndex++;
132
133        // Push output
134        if (mSliceIndex == mXSlices * mYSlices) {
135            pushOutput("image", mOutputFrame);
136            mOutputFrame.release();
137            mSliceIndex = 0;
138        }
139    }
140}
141