/*
* Copyright (C) 2010 Simon Andreas Eugster (simon.eu@gmail.com)
* This file is a Frei0r plugin.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/**
* Videos recorded with the Nikon D90 and Firmware 1.0 (which might never
* be updated) show nasty steps in slanting lines.
*
* Lee Wilson's post about how to fix this was the first solution I found
* in the web:
* http://www.dvxuser.com/V6/showthread.php?t=149663
* Thank you very much.
*
* Mike Martucci used some C code to remove the steps together with
* ffmpeg, to be found here: http://www.epsilonic.com/d90fix/.
*
* The «magic numbers», called slices here, have been developed by
* buildyo and cgipro, also in the dvxuser forum.
*
*
* This plugin «skips» the scaling step by using linear interpolation
* on the whole image directly.
*
* More accurate:
* The PROBLEM is that Nikon did not scale the videos directly but
* simply used some pixel rows and skipped others, as it seems.
* In regular distances the number of skipped rows is higher than
* usual, creating a gap that leads to steps on sloped lines.
*
* The SLICES describe rows belonging together, i.e. not having a
* big gap between.
*
* The IDEA behind fixing the stair steps is to insert
* a new line between two slices (there are a total of 82 slices),
* filling the line by interpolation, and scaling the image
* (which is now 720+81 pixels high) down to a height of 720 pixels
* again. (Only rows are affected, columns are not, mystically.)
*
*
* This filling followed by scaling can also be done
* WITHOUT SCALING.
*
* Terminology:
* H original height
* N number of slice lines (#slices-1)
*
* 1 Create a MESH containing points representing the position of each
* row. Initially it is just [0 1 2 3 4 ...].
* Length: H
*
* 2 Insert the SLICE LINES into this mesh after each slice. If e.g.
* the first slice is of size 3, then we get [0 1 2 2.5 3 4 ...].
* The new slice line is located between the 3rd and 4th line
* (i.e. line 2 and line 3). 2.5 will be interpolated using
* LINEAR INTERPOLATION:
* 0.5 * the values of line 2
* 0.5 * the values of line 3
* Analogous would a value of 2.1 mean
* 0.9 * the value of line 2
* 0.1 * the value of line 3
* Length: H+N
*
* 3 SCALE the mesh BACK to size H again.
* The mesh might now look like that:
* [0.08 1.25 2.2 2.8 3.75 4.9 ...]
* It gets denser around the inserted slice line and looser in the
* middle of the slices.
* Length: H
*
* This mesh can be reused now for every frame. What needs to be done
* for each frame is
* 4 Interpolate the color values using the mesh values. Continuing
* the example above, this e.g. means for the pixels in the first
* row (line) of the target frame:
* For each color, the new color is
* 0.92 * color of line 0
* 0.08 * color of line 1
* And, for the second row:
* For each color, the new color value is
* 0.75 * color of line 1
* 0.25 * color of line 2
* Note that for a mesh value a.b,
* a is the lower line number and
* (1-b) is the factor for the lower line.
*
*/
#include "frei0r.hpp"
#include <stdio.h>
#include <math.h>
static int slices720p[] = {7,9,9,8,9,9,9,9,9,8,9,9,9,9,8,9,9,9,9,9,8,9,9,9,9,
9,8,9,9,9,9,9,8,9,9,9,9,9,8,9,9,9,9,8,9,9,9,9,9,8,
9,9,9,9,9,8,9,9,9,9,9,8,9,9,9,9,9,8,9,9,9,9,8,9,9,
9,9,9,8,9,9,7};
class D90StairsteppingFix : public frei0r::filter
{
public:
D90StairsteppingFix(unsigned int width, unsigned int height)
{
m_mesh = new float[height];
if (height == 720) {
/** Number of newly inserted lines: always between two slices (so #slices-1) */
int sliceLinesNumber = (sizeof slices720p)/(sizeof slices720p[0]) - 1;
/** The height of the image after inserting the slice lines */
int newHeight = height + sliceLinesNumber;
// printf("%d slice lines, %d total new lines\n", sliceLinesNumber, newHeight);
/**
* The position of each line including slice lines.
* Slice lines are inserted between two lines (e.g. between 6 and 7)
* and therefore get the number (line1+line2)/2, here 6.5.
* This positions will later be used for interpolation.
*/
float *filled = (float *) malloc(newHeight * sizeof(float));
int count = 0;
int index = 0;
for (int i = 0; i < sliceLinesNumber+1; i++) {
for (int j = 0; j < slices720p[i]; j++) {
filled[index] = count;
count++;
index++;
}
if (count < newHeight) {
filled[index] = count - 0.5;
index++;
}
}
// for (int i = 0; i < newHeight; i++) {
// printf("inserted Lines: %f at %d\n", filled[i], i);
// }
/**
* Calculate scaling numbers to scale the full height matrix
* with the slice lines down to the original height (720p).
*/
float *downScaling = (float *) malloc(height * sizeof(float));
float scaleFactor = (float) newHeight/height;
// printf("scale factor: %f\n", scaleFactor);
for (unsigned int i = 0; i < height; i++) {
downScaling[i] = (float) (((2*i+1)*scaleFactor)-1)/2;
// printf("scaled: %f at %d\n", downScaling[i], i);
}
/**
* Finish the mesh by scaling the H+N sized mesh to size H,
* using linear interpolation and the previously
* calculated scaling numbers.
*/
float offset;
for (unsigned int i = 0; i < height; i++) {
index = floor(downScaling[i]);
offset = downScaling[i] - index;
m_mesh[i] = (1-offset)*filled[index] + offset*filled[index+1];
// printf("%f at %d with weights %f and %f\n", m_mesh[i], i, (1-offset)*downScaling[i], offset*downScaling[i+1]);
}
free(downScaling);
free(filled);
} else {
// Not a 720p file.
}
}
~D90StairsteppingFix()
{
delete[] m_mesh;
}
virtual void update(double time,
uint32_t* out,
const uint32_t* in)
{
if (height == 720) {
// printf("Converting.\n");
float factor;
int index;
unsigned char *cvA, *cvB, *cvOut;
for (unsigned int line = 0; line < height; line++) {
index = floor(m_mesh[line]);
factor = (float) m_mesh[line] - index;
cvA = (unsigned char*) &in[width*index];
cvB = (unsigned char*) &in[width*(index+1)];
cvOut = (unsigned char*) &out[width*line];
for (unsigned int pixel = 0; pixel < width*4; pixel++) {
// Use linear interpolation on the colours
// Use pointer arithmetic. Colour values are stored
// as AABBGGRR in the uint32_t values.
// Convert each colour separately.
*cvOut = floor((float)(1-factor)*(*cvA) + (factor)*(*cvB));
cvA++;
cvB++;
cvOut++;
}
}
std::copy(in + width*(height-1), in+width*height, out + width*(height-1));
} else {
// Not a 720p file -> Cannot work, do nothing.
// printf("Just copying. Height is %d.\n", height);
std::copy(in, in + width*height, out);
}
}
private:
float *m_mesh;
};
frei0r::construct<D90StairsteppingFix> plugin("Nikon D90 Stairstepping fix",
"Removes the Stairstepping from Nikon D90 videos (720p only) by interpolation",
"Simon A. Eugster (Granjow)",
0,2,
F0R_COLOR_MODEL_RGBA8888);