Skip to content

Commit

Permalink
Merge pull request #9729 from Snuffleupagus/gulp-image_decoders
Browse files Browse the repository at this point in the history
Add a `gulp image_decoders` command to package the image decoders (i.e. jpg.js, jpx.js, jbig2.js) separately, and publish them in pdfjs-dist
  • Loading branch information
timvandermeij authored Jun 26, 2018
2 parents 66ffdc4 + 5984c24 commit 14b69a4
Show file tree
Hide file tree
Showing 10 changed files with 288 additions and 14 deletions.
Binary file added examples/image_decoders/fish.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
40 changes: 40 additions & 0 deletions examples/image_decoders/jpeg_viewer.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<!DOCTYPE html>
<!--
Copyright 2018 Mozilla Foundation
Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<html dir="ltr" mozdisallowselectionprint>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="google" content="notranslate">
<title>PDF.js standalone JpegImage parser</title>

<style>
body {
background-color: #808080;
margin: 0;
padding: 0;
}
</style>

<script src="../../node_modules/pdfjs-dist/image_decoders/pdf.image_decoders.js"></script>
</head>

<body tabindex="1">
<canvas id="jpegCanvas" width="0" height="0"></canvas>

<script src="jpeg_viewer.js"></script>
</body>
</html>
75 changes: 75 additions & 0 deletions examples/image_decoders/jpeg_viewer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/* Copyright 2018 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

if (!pdfjsImageDecoders.JpegImage) {
alert('Please build the pdfjs-dist library using `gulp dist-install`');
}

var JPEG_IMAGE = 'fish.jpg';

var jpegCanvas = document.getElementById('jpegCanvas');
var jpegCtx = jpegCanvas.getContext('2d');

// Load the image data, and convert it to a Uint8Array.
//
var nonBinaryRequest = false;
var request = new XMLHttpRequest();
request.open('GET', JPEG_IMAGE, false);
try {
request.responseType = 'arraybuffer';
nonBinaryRequest = request.responseType !== 'arraybuffer';
} catch (e) {
nonBinaryRequest = true;
}
if (nonBinaryRequest && request.overrideMimeType) {
request.overrideMimeType('text/plain; charset=x-user-defined');
}
request.send(null);

var typedArrayImage;
if (nonBinaryRequest) {
var str = request.responseText, length = str.length;
var bytes = new Uint8Array(length);
for (var i = 0; i < length; ++i) {
bytes[i] = str.charCodeAt(i) & 0xFF;
}
typedArrayImage = bytes;
} else {
typedArrayImage = new Uint8Array(request.response);
}

// Parse the image data using `JpegImage`.
//
var jpegImage = new pdfjsImageDecoders.JpegImage();
jpegImage.parse(typedArrayImage);

var width = jpegImage.width, height = jpegImage.height;
var jpegData = jpegImage.getData(width, height, /* forceRGB = */ true);

// Render the JPEG image on a <canvas>.
//
var imageData = jpegCtx.createImageData(width, height);
var imageBytes = imageData.data;
for (var i = 0, j = 0, ii = width * height * 4; i < ii;) {
imageBytes[i++] = jpegData[j++];
imageBytes[i++] = jpegData[j++];
imageBytes[i++] = jpegData[j++];
imageBytes[i++] = 255;
}
jpegCanvas.width = width, jpegCanvas.height = height;
jpegCtx.putImageData(imageData, 0, 0);

45 changes: 43 additions & 2 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ var BASELINE_DIR = BUILD_DIR + 'baseline/';
var MOZCENTRAL_BASELINE_DIR = BUILD_DIR + 'mozcentral.baseline/';
var GENERIC_DIR = BUILD_DIR + 'generic/';
var COMPONENTS_DIR = BUILD_DIR + 'components/';
var IMAGE_DECODERS_DIR = BUILD_DIR + 'image_decoders';
var MINIFIED_DIR = BUILD_DIR + 'minified/';
var JSDOC_BUILD_DIR = BUILD_DIR + 'jsdoc/';
var GH_PAGES_DIR = BUILD_DIR + 'gh-pages/';
Expand All @@ -70,7 +71,7 @@ var builder = require('./external/builder/builder.js');
var CONFIG_FILE = 'pdfjs.config';
var config = JSON.parse(fs.readFileSync(CONFIG_FILE).toString());

// Default Autoprefixer config used for generic, components, minifed-pre
// Default Autoprefixer config used for generic, components, minified-pre
var AUTOPREFIXER_CONFIG = {
browsers: [
'last 2 versions',
Expand All @@ -96,6 +97,7 @@ var DEFINES = {
COMPONENTS: false,
LIB: false,
SKIP_BABEL: false,
IMAGE_DECODERS: false,
};

function safeSpawnSync(command, parameters, options) {
Expand Down Expand Up @@ -306,6 +308,22 @@ function createComponentsBundle(defines) {
.pipe(replaceJSRootName(componentsAMDName, 'pdfjsViewer'));
}

function createImageDecodersBundle(defines) {
var imageDecodersAMDName = 'pdfjs-dist/image_decoders/pdf.image_decoders';
var imageDecodersOutputName = 'pdf.image_decoders.js';

var componentsFileConfig = createWebpackConfig(defines, {
filename: imageDecodersOutputName,
library: imageDecodersAMDName,
libraryTarget: 'umd',
umdNamedDefine: true,
});
return gulp.src('./src/pdf.image_decoders.js')
.pipe(webpack2Stream(componentsFileConfig))
.pipe(replaceWebpackRequire())
.pipe(replaceJSRootName(imageDecodersAMDName, 'pdfjsImageDecoders'));
}

function checkFile(path) {
try {
var stat = fs.lstatSync(path);
Expand Down Expand Up @@ -631,6 +649,15 @@ gulp.task('components', ['buildnumber'], function () {
]);
});

gulp.task('image_decoders', ['buildnumber'], function() {
console.log();
console.log('### Creating image decoders');
var defines = builder.merge(DEFINES, { GENERIC: true,
IMAGE_DECODERS: true, });

return createImageDecodersBundle(defines).pipe(gulp.dest(IMAGE_DECODERS_DIR));
});

gulp.task('minified-pre', ['buildnumber', 'locale'], function () {
console.log();
console.log('### Creating minified viewer');
Expand All @@ -641,6 +668,8 @@ gulp.task('minified-pre', ['buildnumber', 'locale'], function () {
return merge([
createBundle(defines).pipe(gulp.dest(MINIFIED_DIR + 'build')),
createWebBundle(defines).pipe(gulp.dest(MINIFIED_DIR + 'web')),
createImageDecodersBundle(builder.merge(defines, { IMAGE_DECODERS: true, }))
.pipe(gulp.dest(MINIFIED_DIR + 'image_decoders')),
gulp.src(COMMON_WEB_FILES, { base: 'web/', })
.pipe(gulp.dest(MINIFIED_DIR + 'web')),
gulp.src([
Expand All @@ -666,6 +695,8 @@ gulp.task('minified-post', ['minified-pre'], function () {
var pdfFile = fs.readFileSync(MINIFIED_DIR + '/build/pdf.js').toString();
var pdfWorkerFile =
fs.readFileSync(MINIFIED_DIR + '/build/pdf.worker.js').toString();
var pdfImageDecodersFile = fs.readFileSync(MINIFIED_DIR +
'/image_decoders/pdf.image_decoders.js').toString();
var viewerFiles = {
'pdf.js': pdfFile,
'viewer.js': fs.readFileSync(MINIFIED_DIR + '/web/viewer.js').toString(),
Expand All @@ -684,6 +715,8 @@ gulp.task('minified-post', ['minified-pre'], function () {
UglifyES.minify(pdfFile).code);
fs.writeFileSync(MINIFIED_DIR + '/build/pdf.worker.min.js',
UglifyES.minify(pdfWorkerFile, optsForHugeFile).code);
fs.writeFileSync(MINIFIED_DIR + 'image_decoders/pdf.image_decoders.min.js',
UglifyES.minify(pdfImageDecodersFile).code);

console.log();
console.log('### Cleaning js files');
Expand All @@ -696,6 +729,8 @@ gulp.task('minified-post', ['minified-pre'], function () {
MINIFIED_DIR + '/build/pdf.js');
fs.renameSync(MINIFIED_DIR + '/build/pdf.worker.min.js',
MINIFIED_DIR + '/build/pdf.worker.js');
fs.renameSync(MINIFIED_DIR + '/image_decoders/pdf.image_decoders.min.js',
MINIFIED_DIR + '/image_decoders/pdf.image_decoders.js');
});

gulp.task('minified', ['minified-post']);
Expand Down Expand Up @@ -1138,7 +1173,8 @@ gulp.task('gh-pages-git', ['gh-pages-prepare', 'wintersmith'], function () {

gulp.task('web', ['gh-pages-prepare', 'wintersmith', 'gh-pages-git']);

gulp.task('dist-pre', ['generic', 'components', 'lib', 'minified'], function() {
gulp.task('dist-pre',
['generic', 'components', 'image_decoders', 'lib', 'minified'], function() {
var VERSION = getVersionJSON().version;

console.log();
Expand Down Expand Up @@ -1228,8 +1264,13 @@ gulp.task('dist-pre', ['generic', 'components', 'lib', 'minified'], function() {
gulp.src(MINIFIED_DIR + 'build/pdf.worker.js')
.pipe(rename('pdf.worker.min.js'))
.pipe(gulp.dest(DIST_DIR + 'build/')),
gulp.src(MINIFIED_DIR + 'image_decoders/pdf.image_decoders.js')
.pipe(rename('pdf.image_decoders.min.js'))
.pipe(gulp.dest(DIST_DIR + 'image_decoders/')),
gulp.src(COMPONENTS_DIR + '**/*', { base: COMPONENTS_DIR, })
.pipe(gulp.dest(DIST_DIR + 'web/')),
gulp.src(IMAGE_DECODERS_DIR + '**/*', { base: IMAGE_DECODERS_DIR, })
.pipe(gulp.dest(DIST_DIR + 'image_decoders')),
gulp.src(LIB_DIR + '**/*', { base: LIB_DIR, })
.pipe(gulp.dest(DIST_DIR + 'lib/')),
]);
Expand Down
50 changes: 49 additions & 1 deletion src/core/jbig2.js
Original file line number Diff line number Diff line change
Expand Up @@ -1192,6 +1192,47 @@ var Jbig2Image = (function Jbig2ImageClosure() {
return visitor.buffer;
}

function parseJbig2(data) {
let position = 0, end = data.length;

if (data[position] !== 0x97 || data[position + 1] !== 0x4A ||
data[position + 2] !== 0x42 || data[position + 3] !== 0x32 ||
data[position + 4] !== 0x0D || data[position + 5] !== 0x0A ||
data[position + 6] !== 0x1A || data[position + 7] !== 0x0A) {
throw new Jbig2Error('parseJbig2 - invalid header.');
}

let header = Object.create(null);
position += 8;
const flags = data[position++];
header.randomAccess = !(flags & 1);
if (!(flags & 2)) {
header.numberOfPages = readUint32(data, position);
position += 4;
}

let segments = readSegments(header, data, position, end);
let visitor = new SimpleSegmentVisitor();
processSegments(segments, visitor);

const { width, height, } = visitor.currentPageInfo;
const bitPacked = visitor.buffer;
let imgData = new Uint8ClampedArray(width * height);
let q = 0, k = 0;
for (let i = 0; i < height; i++) {
let mask = 0, buffer;
for (let j = 0; j < width; j++) {
if (!mask) {
mask = 128; buffer = bitPacked[k++];
}
imgData[q++] = (buffer & mask) ? 0 : 255;
mask >>= 1;
}
}

return { imgData, width, height, };
}

function SimpleSegmentVisitor() {}

SimpleSegmentVisitor.prototype = {
Expand Down Expand Up @@ -2095,9 +2136,16 @@ var Jbig2Image = (function Jbig2ImageClosure() {
function Jbig2Image() {}

Jbig2Image.prototype = {
parseChunks: function Jbig2Image_parseChunks(chunks) {
parseChunks(chunks) {
return parseJbig2Chunks(chunks);
},

parse(data) {
const { imgData, width, height, } = parseJbig2(data);
this.width = width;
this.height = height;
return imgData;
},
};

return Jbig2Image;
Expand Down
10 changes: 7 additions & 3 deletions src/core/jpeg_stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ let JpegStream = (function JpegStreamClosure() {
if (this.eof) {
return;
}
let jpegImage = new JpegImage();
let jpegOptions = {
decodeTransform: undefined,
colorTransform: undefined,
};

// Checking if values need to be transformed before conversion.
let decodeArr = this.dict.getArray('Decode', 'D');
Expand All @@ -81,16 +84,17 @@ let JpegStream = (function JpegStreamClosure() {
}
}
if (transformNeeded) {
jpegImage.decodeTransform = transform;
jpegOptions.decodeTransform = transform;
}
}
// Fetching the 'ColorTransform' entry, if it exists.
if (isDict(this.params)) {
let colorTransform = this.params.get('ColorTransform');
if (Number.isInteger(colorTransform)) {
jpegImage.colorTransform = colorTransform;
jpegOptions.colorTransform = colorTransform;
}
}
const jpegImage = new JpegImage(jpegOptions);

jpegImage.parse(this.bytes);
let data = jpegImage.getData(this.drawWidth, this.drawHeight,
Expand Down
12 changes: 6 additions & 6 deletions src/core/jpg.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,9 @@ var JpegImage = (function JpegImageClosure() {
var dctSqrt2 = 5793; // sqrt(2)
var dctSqrt1d2 = 2896; // sqrt(2) / 2

function JpegImage() {
this.decodeTransform = null;
this.colorTransform = -1;
function JpegImage({ decodeTransform = null, colorTransform = -1, } = {}) {
this._decodeTransform = decodeTransform;
this._colorTransform = colorTransform;
}

function buildHuffmanTable(codeLengths, values) {
Expand Down Expand Up @@ -1013,7 +1013,7 @@ var JpegImage = (function JpegImageClosure() {
}

// decodeTransform contains pairs of multiplier (-256..256) and additive
var transform = this.decodeTransform;
const transform = this._decodeTransform;
if (transform) {
for (i = 0; i < dataLength;) {
for (j = 0, k = 0; j < numComponents; j++, i++, k += 2) {
Expand All @@ -1030,7 +1030,7 @@ var JpegImage = (function JpegImageClosure() {
return !!this.adobe.transformCode;
}
if (this.numComponents === 3) {
if (this.colorTransform === 0) {
if (this._colorTransform === 0) {
// If the Adobe transform marker is not present and the image
// dictionary has a 'ColorTransform' entry, explicitly set to `0`,
// then the colours should *not* be transformed.
Expand All @@ -1039,7 +1039,7 @@ var JpegImage = (function JpegImageClosure() {
return true;
}
// `this.numComponents !== 3`
if (this.colorTransform === 1) {
if (this._colorTransform === 1) {
// If the Adobe transform marker is not present and the image
// dictionary has a 'ColorTransform' entry, explicitly set to `1`,
// then the colours should be transformed.
Expand Down
Loading

0 comments on commit 14b69a4

Please sign in to comment.