/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is JTransforms. * * The Initial Developer of the Original Code is * Piotr Wendykier, Emory University. * Portions created by the Initial Developer are Copyright (C) 2007-2009 * the Initial Developer. All Rights Reserved. * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ package edu.emory.mathcs.jtransforms.dct; import java.util.concurrent.Future; import edu.emory.mathcs.utils.ConcurrencyUtils; /** * Computes 2D Discrete Cosine Transform (DCT) of single precision data. The * sizes of both dimensions can be arbitrary numbers. This is a parallel * implementation of split-radix and mixed-radix algorithms optimized for SMP * systems.
*
* Part of the code is derived from General Purpose FFT Package written by Takuya Ooura * (http://www.kurims.kyoto-u.ac.jp/~ooura/fft.html) * * @author Piotr Wendykier (piotr.wendykier@gmail.com) * */ public class FloatDCT_2D { private int rows; private int columns; private float[] t; private FloatDCT_1D dctColumns, dctRows; private int nt; private int oldNthreads; private boolean isPowerOfTwo = false; private boolean useThreads = false; /** * Creates new instance of FloatDCT_2D. * * @param rows * number of rows * @param columns * number of columns */ public FloatDCT_2D(int rows, int columns) { if (rows <= 1 || columns <= 1) { throw new IllegalArgumentException("rows and columns must be greater than 1"); } this.rows = rows; this.columns = columns; if (rows * columns >= ConcurrencyUtils.getThreadsBeginN_2D()) { this.useThreads = true; } if (ConcurrencyUtils.isPowerOf2(rows) && ConcurrencyUtils.isPowerOf2(columns)) { isPowerOfTwo = true; oldNthreads = ConcurrencyUtils.getNumberOfThreads(); nt = 4 * oldNthreads * rows; if (columns == 2 * oldNthreads) { nt >>= 1; } else if (columns < 2 * oldNthreads) { nt >>= 2; } t = new float[nt]; } dctColumns = new FloatDCT_1D(columns); if (columns == rows) { dctRows = dctColumns; } else { dctRows = new FloatDCT_1D(rows); } } /** * Computes 2D forward DCT (DCT-II) leaving the result in a. * The data is stored in 1D array in row-major order. * * @param a * data to transform * @param scale * if true then scaling is performed */ public void forward(final float[] a, final boolean scale) { int nthreads = ConcurrencyUtils.getNumberOfThreads(); if (isPowerOfTwo) { if (nthreads != oldNthreads) { nt = 4 * nthreads * rows; if (columns == 2 * nthreads) { nt >>= 1; } else if (columns < 2 * nthreads) { nt >>= 2; } t = new float[nt]; oldNthreads = nthreads; } if ((nthreads > 1) && useThreads) { ddxt2d_subth(-1, a, scale); ddxt2d0_subth(-1, a, scale); } else { ddxt2d_sub(-1, a, scale); for (int i = 0; i < rows; i++) { dctColumns.forward(a, i * columns, scale); } } } else { if ((nthreads > 1) && useThreads && (rows >= nthreads) && (columns >= nthreads)) { Future[] futures = new Future[nthreads]; int p = rows / nthreads; for (int l = 0; l < nthreads; l++) { final int firstRow = l * p; final int lastRow = (l == (nthreads - 1)) ? rows : firstRow + p; futures[l] = ConcurrencyUtils.submit(new Runnable() { public void run() { for (int r = firstRow; r < lastRow; r++) { dctColumns.forward(a, r * columns, scale); } } }); } ConcurrencyUtils.waitForCompletion(futures); p = columns / nthreads; for (int l = 0; l < nthreads; l++) { final int firstColumn = l * p; final int lastColumn = (l == (nthreads - 1)) ? columns : firstColumn + p; futures[l] = ConcurrencyUtils.submit(new Runnable() { public void run() { float[] temp = new float[rows]; for (int c = firstColumn; c < lastColumn; c++) { for (int r = 0; r < rows; r++) { temp[r] = a[r * columns + c]; } dctRows.forward(temp, scale); for (int r = 0; r < rows; r++) { a[r * columns + c] = temp[r]; } } } }); } ConcurrencyUtils.waitForCompletion(futures); } else { for (int i = 0; i < rows; i++) { dctColumns.forward(a, i * columns, scale); } float[] temp = new float[rows]; for (int c = 0; c < columns; c++) { for (int r = 0; r < rows; r++) { temp[r] = a[r * columns + c]; } dctRows.forward(temp, scale); for (int r = 0; r < rows; r++) { a[r * columns + c] = temp[r]; } } } } } /** * Computes 2D forward DCT (DCT-II) leaving the result in a. * The data is stored in 2D array. * * @param a * data to transform * @param scale * if true then scaling is performed */ public void forward(final float[][] a, final boolean scale) { int nthreads = ConcurrencyUtils.getNumberOfThreads(); if (isPowerOfTwo) { if (nthreads != oldNthreads) { nt = 4 * nthreads * rows; if (columns == 2 * nthreads) { nt >>= 1; } else if (columns < 2 * nthreads) { nt >>= 2; } t = new float[nt]; oldNthreads = nthreads; } if ((nthreads > 1) && useThreads) { ddxt2d_subth(-1, a, scale); ddxt2d0_subth(-1, a, scale); } else { ddxt2d_sub(-1, a, scale); for (int i = 0; i < rows; i++) { dctColumns.forward(a[i], scale); } } } else { if ((nthreads > 1) && useThreads && (rows >= nthreads) && (columns >= nthreads)) { Future[] futures = new Future[nthreads]; int p = rows / nthreads; for (int l = 0; l < nthreads; l++) { final int firstRow = l * p; final int lastRow = (l == (nthreads - 1)) ? rows : firstRow + p; futures[l] = ConcurrencyUtils.submit(new Runnable() { public void run() { for (int i = firstRow; i < lastRow; i++) { dctColumns.forward(a[i], scale); } } }); } ConcurrencyUtils.waitForCompletion(futures); p = columns / nthreads; for (int l = 0; l < nthreads; l++) { final int firstColumn = l * p; final int lastColumn = (l == (nthreads - 1)) ? columns : firstColumn + p; futures[l] = ConcurrencyUtils.submit(new Runnable() { public void run() { float[] temp = new float[rows]; for (int c = firstColumn; c < lastColumn; c++) { for (int r = 0; r < rows; r++) { temp[r] = a[r][c]; } dctRows.forward(temp, scale); for (int r = 0; r < rows; r++) { a[r][c] = temp[r]; } } } }); } ConcurrencyUtils.waitForCompletion(futures); } else { for (int i = 0; i < rows; i++) { dctColumns.forward(a[i], scale); } float[] temp = new float[rows]; for (int c = 0; c < columns; c++) { for (int r = 0; r < rows; r++) { temp[r] = a[r][c]; } dctRows.forward(temp, scale); for (int r = 0; r < rows; r++) { a[r][c] = temp[r]; } } } } } /** * Computes 2D inverse DCT (DCT-III) leaving the result in a. * The data is stored in 1D array in row-major order. * * @param a * data to transform * @param scale * if true then scaling is performed */ public void inverse(final float[] a, final boolean scale) { int nthreads = ConcurrencyUtils.getNumberOfThreads(); if (isPowerOfTwo) { if (nthreads != oldNthreads) { nt = 4 * nthreads * rows; if (columns == 2 * nthreads) { nt >>= 1; } else if (columns < 2 * nthreads) { nt >>= 2; } t = new float[nt]; oldNthreads = nthreads; } if ((nthreads > 1) && useThreads) { ddxt2d_subth(1, a, scale); ddxt2d0_subth(1, a, scale); } else { ddxt2d_sub(1, a, scale); for (int i = 0; i < rows; i++) { dctColumns.inverse(a, i * columns, scale); } } } else { if ((nthreads > 1) && useThreads && (rows >= nthreads) && (columns >= nthreads)) { Future[] futures = new Future[nthreads]; int p = rows / nthreads; for (int l = 0; l < nthreads; l++) { final int firstRow = l * p; final int lastRow = (l == (nthreads - 1)) ? rows : firstRow + p; futures[l] = ConcurrencyUtils.submit(new Runnable() { public void run() { for (int i = firstRow; i < lastRow; i++) { dctColumns.inverse(a, i * columns, scale); } } }); } ConcurrencyUtils.waitForCompletion(futures); p = columns / nthreads; for (int l = 0; l < nthreads; l++) { final int firstColumn = l * p; final int lastColumn = (l == (nthreads - 1)) ? columns : firstColumn + p; futures[l] = ConcurrencyUtils.submit(new Runnable() { public void run() { float[] temp = new float[rows]; for (int c = firstColumn; c < lastColumn; c++) { for (int r = 0; r < rows; r++) { temp[r] = a[r * columns + c]; } dctRows.inverse(temp, scale); for (int r = 0; r < rows; r++) { a[r * columns + c] = temp[r]; } } } }); } ConcurrencyUtils.waitForCompletion(futures); } else { for (int i = 0; i < rows; i++) { dctColumns.inverse(a, i * columns, scale); } float[] temp = new float[rows]; for (int c = 0; c < columns; c++) { for (int r = 0; r < rows; r++) { temp[r] = a[r * columns + c]; } dctRows.inverse(temp, scale); for (int r = 0; r < rows; r++) { a[r * columns + c] = temp[r]; } } } } } /** * Computes 2D inverse DCT (DCT-III) leaving the result in a. * The data is stored in 2D array. * * @param a * data to transform * @param scale * if true then scaling is performed */ public void inverse(final float[][] a, final boolean scale) { int nthreads = ConcurrencyUtils.getNumberOfThreads(); if (isPowerOfTwo) { if (nthreads != oldNthreads) { nt = 4 * nthreads * rows; if (columns == 2 * nthreads) { nt >>= 1; } else if (columns < 2 * nthreads) { nt >>= 2; } t = new float[nt]; oldNthreads = nthreads; } if ((nthreads > 1) && useThreads) { ddxt2d_subth(1, a, scale); ddxt2d0_subth(1, a, scale); } else { ddxt2d_sub(1, a, scale); for (int i = 0; i < rows; i++) { dctColumns.inverse(a[i], scale); } } } else { if ((nthreads > 1) && useThreads && (rows >= nthreads) && (columns >= nthreads)) { Future[] futures = new Future[nthreads]; int p = rows / nthreads; for (int l = 0; l < nthreads; l++) { final int firstRow = l * p; final int lastRow = (l == (nthreads - 1)) ? rows : firstRow + p; futures[l] = ConcurrencyUtils.submit(new Runnable() { public void run() { for (int i = firstRow; i < lastRow; i++) { dctColumns.inverse(a[i], scale); } } }); } ConcurrencyUtils.waitForCompletion(futures); p = columns / nthreads; for (int l = 0; l < nthreads; l++) { final int firstColumn = l * p; final int lastColumn = (l == (nthreads - 1)) ? columns : firstColumn + p; futures[l] = ConcurrencyUtils.submit(new Runnable() { public void run() { float[] temp = new float[rows]; for (int c = firstColumn; c < lastColumn; c++) { for (int r = 0; r < rows; r++) { temp[r] = a[r][c]; } dctRows.inverse(temp, scale); for (int r = 0; r < rows; r++) { a[r][c] = temp[r]; } } } }); } ConcurrencyUtils.waitForCompletion(futures); } else { for (int r = 0; r < rows; r++) { dctColumns.inverse(a[r], scale); } float[] temp = new float[rows]; for (int c = 0; c < columns; c++) { for (int r = 0; r < rows; r++) { temp[r] = a[r][c]; } dctRows.inverse(temp, scale); for (int r = 0; r < rows; r++) { a[r][c] = temp[r]; } } } } } private void ddxt2d_subth(final int isgn, final float[] a, final boolean scale) { int nthread = ConcurrencyUtils.getNumberOfThreads(); int nt = 4 * rows; if (columns == 2 * nthread) { nt >>= 1; } else if (columns < 2 * nthread) { nthread = columns; nt >>= 2; } final int nthreads = nthread; Future[] futures = new Future[nthread]; for (int i = 0; i < nthread; i++) { final int n0 = i; final int startt = nt * i; futures[i] = ConcurrencyUtils.submit(new Runnable() { public void run() { int idx1, idx2; if (columns > 2 * nthreads) { if (isgn == -1) { for (int c = 4 * n0; c < columns; c += 4 * nthreads) { for (int r = 0; r < rows; r++) { idx1 = r * columns + c; idx2 = startt + rows + r; t[startt + r] = a[idx1]; t[idx2] = a[idx1 + 1]; t[idx2 + rows] = a[idx1 + 2]; t[idx2 + 2 * rows] = a[idx1 + 3]; } dctRows.forward(t, startt, scale); dctRows.forward(t, startt + rows, scale); dctRows.forward(t, startt + 2 * rows, scale); dctRows.forward(t, startt + 3 * rows, scale); for (int r = 0; r < rows; r++) { idx1 = r * columns + c; idx2 = startt + rows + r; a[idx1] = t[startt + r]; a[idx1 + 1] = t[idx2]; a[idx1 + 2] = t[idx2 + rows]; a[idx1 + 3] = t[idx2 + 2 * rows]; } } } else { for (int c = 4 * n0; c < columns; c += 4 * nthreads) { for (int r = 0; r < rows; r++) { idx1 = r * columns + c; idx2 = startt + rows + r; t[startt + r] = a[idx1]; t[idx2] = a[idx1 + 1]; t[idx2 + rows] = a[idx1 + 2]; t[idx2 + 2 * rows] = a[idx1 + 3]; } dctRows.inverse(t, startt, scale); dctRows.inverse(t, startt + rows, scale); dctRows.inverse(t, startt + 2 * rows, scale); dctRows.inverse(t, startt + 3 * rows, scale); for (int r = 0; r < rows; r++) { idx1 = r * columns + c; idx2 = startt + rows + r; a[idx1] = t[startt + r]; a[idx1 + 1] = t[idx2]; a[idx1 + 2] = t[idx2 + rows]; a[idx1 + 3] = t[idx2 + 2 * rows]; } } } } else if (columns == 2 * nthreads) { for (int r = 0; r < rows; r++) { idx1 = r * columns + 2 * n0; idx2 = startt + r; t[idx2] = a[idx1]; t[idx2 + rows] = a[idx1 + 1]; } if (isgn == -1) { dctRows.forward(t, startt, scale); dctRows.forward(t, startt + rows, scale); } else { dctRows.inverse(t, startt, scale); dctRows.inverse(t, startt + rows, scale); } for (int r = 0; r < rows; r++) { idx1 = r * columns + 2 * n0; idx2 = startt + r; a[idx1] = t[idx2]; a[idx1 + 1] = t[idx2 + rows]; } } else if (columns == nthreads) { for (int r = 0; r < rows; r++) { t[startt + r] = a[r * columns + n0]; } if (isgn == -1) { dctRows.forward(t, startt, scale); } else { dctRows.inverse(t, startt, scale); } for (int r = 0; r < rows; r++) { a[r * columns + n0] = t[startt + r]; } } } }); } ConcurrencyUtils.waitForCompletion(futures); } private void ddxt2d_subth(final int isgn, final float[][] a, final boolean scale) { int nthread = ConcurrencyUtils.getNumberOfThreads(); int nt = 4 * rows; if (columns == 2 * nthread) { nt >>= 1; } else if (columns < 2 * nthread) { nthread = columns; nt >>= 2; } final int nthreads = nthread; Future[] futures = new Future[nthread]; for (int i = 0; i < nthread; i++) { final int n0 = i; final int startt = nt * i; futures[i] = ConcurrencyUtils.submit(new Runnable() { public void run() { int idx2; if (columns > 2 * nthreads) { if (isgn == -1) { for (int c = 4 * n0; c < columns; c += 4 * nthreads) { for (int r = 0; r < rows; r++) { idx2 = startt + rows + r; t[startt + r] = a[r][c]; t[idx2] = a[r][c + 1]; t[idx2 + rows] = a[r][c + 2]; t[idx2 + 2 * rows] = a[r][c + 3]; } dctRows.forward(t, startt, scale); dctRows.forward(t, startt + rows, scale); dctRows.forward(t, startt + 2 * rows, scale); dctRows.forward(t, startt + 3 * rows, scale); for (int r = 0; r < rows; r++) { idx2 = startt + rows + r; a[r][c] = t[startt + r]; a[r][c + 1] = t[idx2]; a[r][c + 2] = t[idx2 + rows]; a[r][c + 3] = t[idx2 + 2 * rows]; } } } else { for (int c = 4 * n0; c < columns; c += 4 * nthreads) { for (int r = 0; r < rows; r++) { idx2 = startt + rows + r; t[startt + r] = a[r][c]; t[idx2] = a[r][c + 1]; t[idx2 + rows] = a[r][c + 2]; t[idx2 + 2 * rows] = a[r][c + 3]; } dctRows.inverse(t, startt, scale); dctRows.inverse(t, startt + rows, scale); dctRows.inverse(t, startt + 2 * rows, scale); dctRows.inverse(t, startt + 3 * rows, scale); for (int r = 0; r < rows; r++) { idx2 = startt + rows + r; a[r][c] = t[startt + r]; a[r][c + 1] = t[idx2]; a[r][c + 2] = t[idx2 + rows]; a[r][c + 3] = t[idx2 + 2 * rows]; } } } } else if (columns == 2 * nthreads) { for (int r = 0; r < rows; r++) { idx2 = startt + r; t[idx2] = a[r][2 * n0]; t[idx2 + rows] = a[r][2 * n0 + 1]; } if (isgn == -1) { dctRows.forward(t, startt, scale); dctRows.forward(t, startt + rows, scale); } else { dctRows.inverse(t, startt, scale); dctRows.inverse(t, startt + rows, scale); } for (int r = 0; r < rows; r++) { idx2 = startt + r; a[r][2 * n0] = t[idx2]; a[r][2 * n0 + 1] = t[idx2 + rows]; } } else if (columns == nthreads) { for (int r = 0; r < rows; r++) { t[startt + r] = a[r][n0]; } if (isgn == -1) { dctRows.forward(t, startt, scale); } else { dctRows.inverse(t, startt, scale); } for (int r = 0; r < rows; r++) { a[r][n0] = t[startt + r]; } } } }); } ConcurrencyUtils.waitForCompletion(futures); } private void ddxt2d0_subth(final int isgn, final float[] a, final boolean scale) { final int nthreads = ConcurrencyUtils.getNumberOfThreads() > rows ? rows : ConcurrencyUtils.getNumberOfThreads(); Future[] futures = new Future[nthreads]; for (int i = 0; i < nthreads; i++) { final int n0 = i; futures[i] = ConcurrencyUtils.submit(new Runnable() { public void run() { if (isgn == -1) { for (int r = n0; r < rows; r += nthreads) { dctColumns.forward(a, r * columns, scale); } } else { for (int r = n0; r < rows; r += nthreads) { dctColumns.inverse(a, r * columns, scale); } } } }); } ConcurrencyUtils.waitForCompletion(futures); } private void ddxt2d0_subth(final int isgn, final float[][] a, final boolean scale) { final int nthreads = ConcurrencyUtils.getNumberOfThreads() > rows ? rows : ConcurrencyUtils.getNumberOfThreads(); Future[] futures = new Future[nthreads]; for (int i = 0; i < nthreads; i++) { final int n0 = i; futures[i] = ConcurrencyUtils.submit(new Runnable() { public void run() { if (isgn == -1) { for (int r = n0; r < rows; r += nthreads) { dctColumns.forward(a[r], scale); } } else { for (int r = n0; r < rows; r += nthreads) { dctColumns.inverse(a[r], scale); } } } }); } ConcurrencyUtils.waitForCompletion(futures); } private void ddxt2d_sub(int isgn, float[] a, boolean scale) { int idx1, idx2; if (columns > 2) { if (isgn == -1) { for (int c = 0; c < columns; c += 4) { for (int r = 0; r < rows; r++) { idx1 = r * columns + c; idx2 = rows + r; t[r] = a[idx1]; t[idx2] = a[idx1 + 1]; t[idx2 + rows] = a[idx1 + 2]; t[idx2 + 2 * rows] = a[idx1 + 3]; } dctRows.forward(t, 0, scale); dctRows.forward(t, rows, scale); dctRows.forward(t, 2 * rows, scale); dctRows.forward(t, 3 * rows, scale); for (int r = 0; r < rows; r++) { idx1 = r * columns + c; idx2 = rows + r; a[idx1] = t[r]; a[idx1 + 1] = t[idx2]; a[idx1 + 2] = t[idx2 + rows]; a[idx1 + 3] = t[idx2 + 2 * rows]; } } } else { for (int c = 0; c < columns; c += 4) { for (int r = 0; r < rows; r++) { idx1 = r * columns + c; idx2 = rows + r; t[r] = a[idx1]; t[idx2] = a[idx1 + 1]; t[idx2 + rows] = a[idx1 + 2]; t[idx2 + 2 * rows] = a[idx1 + 3]; } dctRows.inverse(t, 0, scale); dctRows.inverse(t, rows, scale); dctRows.inverse(t, 2 * rows, scale); dctRows.inverse(t, 3 * rows, scale); for (int r = 0; r < rows; r++) { idx1 = r * columns + c; idx2 = rows + r; a[idx1] = t[r]; a[idx1 + 1] = t[idx2]; a[idx1 + 2] = t[idx2 + rows]; a[idx1 + 3] = t[idx2 + 2 * rows]; } } } } else if (columns == 2) { for (int r = 0; r < rows; r++) { idx1 = r * columns; t[r] = a[idx1]; t[rows + r] = a[idx1 + 1]; } if (isgn == -1) { dctRows.forward(t, 0, scale); dctRows.forward(t, rows, scale); } else { dctRows.inverse(t, 0, scale); dctRows.inverse(t, rows, scale); } for (int r = 0; r < rows; r++) { idx1 = r * columns; a[idx1] = t[r]; a[idx1 + 1] = t[rows + r]; } } } private void ddxt2d_sub(int isgn, float[][] a, boolean scale) { int idx2; if (columns > 2) { if (isgn == -1) { for (int c = 0; c < columns; c += 4) { for (int r = 0; r < rows; r++) { idx2 = rows + r; t[r] = a[r][c]; t[idx2] = a[r][c + 1]; t[idx2 + rows] = a[r][c + 2]; t[idx2 + 2 * rows] = a[r][c + 3]; } dctRows.forward(t, 0, scale); dctRows.forward(t, rows, scale); dctRows.forward(t, 2 * rows, scale); dctRows.forward(t, 3 * rows, scale); for (int r = 0; r < rows; r++) { idx2 = rows + r; a[r][c] = t[r]; a[r][c + 1] = t[idx2]; a[r][c + 2] = t[idx2 + rows]; a[r][c + 3] = t[idx2 + 2 * rows]; } } } else { for (int c = 0; c < columns; c += 4) { for (int r = 0; r < rows; r++) { idx2 = rows + r; t[r] = a[r][c]; t[idx2] = a[r][c + 1]; t[idx2 + rows] = a[r][c + 2]; t[idx2 + 2 * rows] = a[r][c + 3]; } dctRows.inverse(t, 0, scale); dctRows.inverse(t, rows, scale); dctRows.inverse(t, 2 * rows, scale); dctRows.inverse(t, 3 * rows, scale); for (int r = 0; r < rows; r++) { idx2 = rows + r; a[r][c] = t[r]; a[r][c + 1] = t[idx2]; a[r][c + 2] = t[idx2 + rows]; a[r][c + 3] = t[idx2 + 2 * rows]; } } } } else if (columns == 2) { for (int r = 0; r < rows; r++) { t[r] = a[r][0]; t[rows + r] = a[r][1]; } if (isgn == -1) { dctRows.forward(t, 0, scale); dctRows.forward(t, rows, scale); } else { dctRows.inverse(t, 0, scale); dctRows.inverse(t, rows, scale); } for (int r = 0; r < rows; r++) { a[r][0] = t[r]; a[r][1] = t[rows + r]; } } } }