diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..8dc6807 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,21 @@ +{ + "rules": { + "no-console": "off", + "indent": [ "error", 2 ], + "quotes": [ "error", "single" ], + "semi": ["error", "always"], + "linebreak-style": [ "error", "unix" ] + }, + "env": { + "es6": true, + "node": true, + "mocha": true, + "jasmine": true + }, + "ecmaFeatures": { + "modules": true, + "experimentalObjectRestSpread": true, + "impliedStrict": true + }, + "extends": "eslint:recommended" +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b351354 --- /dev/null +++ b/.gitignore @@ -0,0 +1,129 @@ + +# Created by https://www.gitignore.io/api/osx,node,linux,windows + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + + +### OSX ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.gitignore.io/api/osx,node,linux,windows diff --git a/README.md b/README.md index fd37a07..55a4c2e 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,46 @@ -![CF](https://camo.githubusercontent.com/70edab54bba80edb7493cad3135e9606781cbb6b/687474703a2f2f692e696d6775722e636f6d2f377635415363382e706e67) Lab 04: Bitmap Transformer -=== - -## To Submit this Assignment -* have team leader fork this repository -* have team leader add team members as collaborators to the team fork -* team members should clone team fork -* write all of your code in a directory name `bitmap-` + `` **e.g.** `bitmap-weasels` -* submit a pull request to this repository when done -* each person will submit a link to their own PR in canvas -* each person write a question and observation on canvas - -#### Rubric: -* **tests:** 3pts -* **gulpfile/package.json:** 2pts -* **read bitmap meta data:** 5pts -* **successfully apply transforms:** 5pts -* **project design and organization:** 5pts - -## Description - -For this assignment you will be building a bitmap (`.bmp`) reader and transformer. It will read a bitmap in from disk, run one or more color transforms on the bitmap and then write it out to a new file. This project will require the use of node buffers in order to manipulate binary data. Your project should include tests, as well as a `gulpfile.js`, `package.json`, `.eslintrc`, `README.md`, and a `.gitignore`. Make sure to run all your code through eslint. The process will look something like this: - -1. open the original bitmap file using fs and read it into a buffer -2. convert the buffer header data into a Javascript Object (using constructors) -3. run a transform on the buffer directly -4. write the buffer to a new file - -The wikipedia article found here [Bitmap Specification](https://en.wikipedia.org/wiki/BMP_file_format) describes the byte specification of a "windows bitmap file." We'll be working with the simplest version, meaning no compression. - -* your project should have three ***(or more)*** transforms -* invert the colors (***hint:*** subtract every color value from the max color value which is 255), -* grayscale the colors (***hint:*** multiply each color value by a constant, just make sure your values don't go over 255) -* (red|green|blue)scale the colors (***hint:*** same as above but only multiply one of the colors) - -## Bonus: - -* ability to handle various sized bitmap -* ability to handle LE and BE computers with a single if statement -* utilizes a command line interface (CLI) -* CLI can select the transforms +# BITMAP TRANSFORMER +Built by TEAM MEGATRON - Khalid Mohamud, Dana Kulp, Regan O'Neill + +This program utilizes node.js to read a bitmap data stream, manipulate the stream, and create a new bitmap image to the filesystem. + +# Features Include: + + - Read bitmap data stream from operating system + - copy data as Buffer object and transform into different data structures in order to transform image. + - use object constructor to allow metadata to persist and be reused within application + - utilize three methods to transform the original file: + 1) INVERT COLORS + [Imgur](http://i.imgur.com/GPZ8S8J.png) + 2) GREENSCALE IMAGE + [Imgur](http://i.imgur.com/vQXy11e.png) + 3) GRAYSCALE IMAGE + [Imgur](http://i.imgur.com/W2Tu028.png) + +You can also: + - Run individual methods directly from the CLI to create one (or more) new bitmap files. + - run automation testing tools with gulp + +### Tech + +* [node.js] - evented I/O for the backend +* [Gulp] - the streaming build system + +### Installation + +Bitmap Transformer requires [Node.js](https://nodejs.org/) v6+ to run. + +Install the dependencies and devDependencies and start the server. + +```sh +$ cd bitmap-megatron +$ npm i +$ node index +[optional single commands include: 'invert', 'grayscale', 'greenscale'] +``` +License +---- + +MIT + + +**Free Software, Hell Yeah!** diff --git a/img/palette-bitmap.bmp b/assets/palette-bitmap.bmp similarity index 100% rename from img/palette-bitmap.bmp rename to assets/palette-bitmap.bmp diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..cc4c6b1 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,19 @@ +'use strict'; + +const gulp = require('gulp'); +const eslint = require('gulp-eslint'); +const mocha = require('gulp-mocha'); + +gulp.task('test', function(){ + gulp.src('./test/*-test.js',{read:false}) + .pipe(mocha({reporter:'spec'})); +});//end test task + +gulp.task('lint', function(){ + return gulp.src(['**/*.js', '!node_modules']) + .pipe(eslint()) + .pipe(eslint.format()) + .pipe(eslint.failAfterError()); +}); + +gulp.task('default', ['test', 'lint']); diff --git a/index.js b/index.js new file mode 100644 index 0000000..12ec6b6 --- /dev/null +++ b/index.js @@ -0,0 +1,37 @@ + +'use strict'; + +const EE = require('events'); +const ee = new EE(); +const fs = require('fs'); +const objConstructor = require(`${__dirname}/models/bitmap-object.js`).ImageObj; +const invert = require(`${__dirname}/lib/bitmap-transform.js`); +const greenScale = require(`${__dirname}/lib/bitmap-greenscale.js`); +const grayScale = require(`${__dirname}/lib/bitmap-grayscale.js`); + +ee.on('getFile', function() { + fs.readFile(`${__dirname}/assets/palette-bitmap.bmp`, function(err, data) { + if (err) throw err; + ee.emit('objCreate', data); + }); +}); + +ee.on('objCreate', function(data) { + let testObj = new objConstructor(data); + ee.emit('transformObj', data); +}); + +ee.on('transformObj', function(obj) { + for(var i = 2; i < process.argv.length; i++){ + if (process.argv[i] === 'invert' ) invert(obj); + if (process.argv[i] === 'grayscale') grayScale(obj); + if (process.argv[i] === 'greenscale') greenScale(obj); + } + if (!process.argv[2]) { + invert(obj); + grayScale(obj); + greenScale(obj); + } +}); + +ee.emit('getFile'); diff --git a/lib/bitmap-grayscale.js b/lib/bitmap-grayscale.js new file mode 100644 index 0000000..d6a4f3d --- /dev/null +++ b/lib/bitmap-grayscale.js @@ -0,0 +1,35 @@ +'use strict'; + +const fs = require('fs'); + +module.exports = function grayScale(obj) { + let bufferArray = new Int32Array(obj); + let metaData = bufferArray.slice(0, 54); + let colorTable = bufferArray.slice(54, 1078); + let pixelArray = bufferArray.slice(1078); + + let manipulateTable = function(arr, cb) { + for (let i = 0; i < arr.length; i += 4) { + let scale = colorTable[i]; + colorTable[i + 1] = scale; + colorTable[i + 2] = scale; + } + cb(); + }; + + let createBuffer = function() { + let meta = Buffer.from(metaData); + let pixel = Buffer.from(pixelArray); + let redScale = Buffer.from(colorTable); + let newBuffer = Buffer.concat([meta, redScale, pixel]); + writeGrayFile(newBuffer); + }; + + let writeGrayFile = function(obj) { + console.log('gray'); + fs.writeFile(`${__dirname}/../assets/gray-scale.bmp`, obj, function(err) { + if (err) throw err; + }); + }; + manipulateTable(colorTable, createBuffer); +}; diff --git a/lib/bitmap-greenscale.js b/lib/bitmap-greenscale.js new file mode 100644 index 0000000..662bf06 --- /dev/null +++ b/lib/bitmap-greenscale.js @@ -0,0 +1,34 @@ +'use strict'; + +const fs = require('fs'); + +module.exports = function greenScale(obj) { + let bufferArray = new Int32Array(obj); + let metaData = bufferArray.slice(0, 54); + let colorTable = bufferArray.slice(54, 1078); + let pixelArray = bufferArray.slice(1078); + + let manipulateTable = function(arr) { + for (var i = 1; i < arr.length; i += 4) { + colorTable[i] = 255; + } + createBuffer(); + }; + + let createBuffer = function() { + let meta = Buffer.from(metaData); + let pixel = Buffer.from(pixelArray); + let redScale = Buffer.from(colorTable); + let newBuffer = Buffer.concat([meta, redScale, pixel]); + fileHelper(newBuffer); + }; + + + let fileHelper = function(obj) { + console.log('green'); + fs.writeFile(`${__dirname}/../assets/green.bmp`, obj, function(err) { + if (err) throw err; + }); + }; + manipulateTable(colorTable); +}; diff --git a/lib/bitmap-transform.js b/lib/bitmap-transform.js new file mode 100644 index 0000000..5f96a90 --- /dev/null +++ b/lib/bitmap-transform.js @@ -0,0 +1,28 @@ +'use strict'; + +const fs = require('fs'); + +module.exports = function invertColors(obj) { + let bufferArray = new Int32Array(obj); + + let invertTransform = bufferArray.reduce(function(acc, ele, index) { + if (index < 54) acc.push(ele); + if (index >= 54 && index < 1078) { + let inverted = 255 - ele; + acc.push(inverted); + } + if (index >= 1078) acc.push(ele); + return acc; + }, []); + + let newBuffer = Buffer.from(invertTransform); + + + let invertFileHelper = function(obj) { + console.log('invert'); + fs.writeFile(`${__dirname}/../assets/dana-bitmap.bmp`, obj, function(err) { + if (err) throw err; + }); + }; + invertFileHelper(newBuffer); +}; diff --git a/models/bitmap-object.js b/models/bitmap-object.js new file mode 100644 index 0000000..35b121f --- /dev/null +++ b/models/bitmap-object.js @@ -0,0 +1,26 @@ +'use strict'; + +module.exports = exports = {}; + +// exports.newColorArray = []; + +exports.bmpArray = []; + +exports.ImageObj = function(buffer) { + this.type = buffer.toString('utf-8', 0, 2); + this.pixelArrayOffset = buffer.readInt32LE(10); + this.headerSize = buffer.readInt32LE(14); + this.imgWidth = buffer.readInt32LE(18); + this.imgHeight = buffer.readInt32LE(22); + this.colorPlanes = buffer.readInt16LE(26); + this.bitsPerPixel = buffer.readInt16LE(28); + this.imgSize = buffer.readInt32LE(34); + this.heightRez = buffer.readInt32LE(38); + this.vertRez = buffer.readInt32LE(42); + this.numColors = buffer.readInt32LE(46); + this.imptColors = buffer.readInt32LE(50); + this.metaData = buffer.slice(0, 54); + this.colorTable = buffer.slice(54, 1078); + this.pixelArray = buffer.slice(1078); + exports.bmpArray.push(this); +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..c159248 --- /dev/null +++ b/package.json @@ -0,0 +1,32 @@ +{ + "name": "bitmap-megatron", + "version": "1.0.0", + "description": "![CF](https://camo.githubusercontent.com/70edab54bba80edb7493cad3135e9606781cbb6b/687474703a2f2f692e696d6775722e636f6d2f377635415363382e706e67) Lab 04: Bitmap Transformer - TEAM MEGATRON === Members: Khalid Mohamud, Dana Kulp, Regan O'Neill", + "main": "index.js", + "directories": { + "test": "test" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/reganoneill/04-bitmap_transformer.git" + }, + "keywords": [], + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/reganoneill/04-bitmap_transformer/issues" + }, + "homepage": "https://github.com/reganoneill/04-bitmap_transformer#readme", + "devDependencies": { + "chai": "^3.5.0", + "gulp": "^3.9.1", + "gulp-eslint": "^3.0.1", + "gulp-mocha": "^3.0.1", + "mocha": "^3.2.0", + "sinon": "^1.17.7", + "sinon-chai": "^2.8.0" + } +} diff --git a/test/buffer-test.js b/test/buffer-test.js new file mode 100644 index 0000000..81c1351 --- /dev/null +++ b/test/buffer-test.js @@ -0,0 +1,88 @@ +'use strict'; + +const sinon = require('sinon'); +const sinonChai = require('sinon-chai'); +const EventEmitter = require('events').EventEmitter; +const bmpReader = require(`${__dirname}/../index.js`); +const expect = require('chai').expect; +const fs = require('fs'); +const originalImage = fs.readFile(`${__dirname}/../assets/palette-bitmap.bmp`, function(err, data){ + if(err) throw err; + return data; +}); +const bmpArray = require('../models/bitmap-object.js').bmpArray; + +describe('EventEmitter', function(){ + describe('#emit-event', function(){ + it('should invoke callback chain', function(done){ + var spy = sinon.spy(); + var emitter = new EventEmitter; + emitter.on('getFile', spy); + emitter.emit('getFile'); + // expect(spy).to.equal.true; + expect(spy).to.not.throw(Error); + done(); + }); + }); + +}); + +describe('Buffer tests', function() { + describe('bitmap array', function(){ + it('should be populated with image objects', function(done){ + console.log(bmpArray.length); + expect(bmpArray.length > 0).to.be.equal(true); + done(); + }); + }); + describe('Green scale image', function(){ + it('should be generated', function(done){ + fs.readFile(`${__dirname}/../assets/green.bmp`, function(err, data){ + console.log(err); + expect(err).to.be.equal(null); + expect(Buffer.isBuffer(data)).to.be.equal(true); + done(); + }); + }); + it('should be the same size as the original image', function(done){ + fs.readFile(`${__dirname}/../assets/green.bmp`, function(err, data){ + expect(err).to.be.equal(null); + console.log(data.length); + expect(data.length).to.be.equal(11078); + done(); + }); + }); + }); + describe('Inverted image', function(){ + it('should be generated', function(done){ + fs.readFile(`${__dirname}/../assets/dana-bitmap.bmp`, function(err, data){ + expect(err).to.equal(null); + expect(Buffer.isBuffer(data)).to.be.equal(true); + done(); + }); + }); + it('should be the same size as the original image', function(done){ + fs.readFile(`${__dirname}/../assets/dana-bitmap.bmp`, function(err, data){ + expect(err).to.equal(null); + expect(data.length).to.be.equal(11078); + done(); + }); + }); + }); + describe('Gray scale image', function(){ + it('should be generated', function(done){ + fs.readFile(`${__dirname}/../assets/gray-scale.bmp`, function(err, data){ + expect(err).to.equal(null); + expect(Buffer.isBuffer(data)).to.be.equal(true); + done(); + }); + }); + it('should be the same size as the original image', function(done){ + fs.readFile(`${__dirname}/../assets/gray-scale.bmp`, function(err, data){ + expect(err).to.equal(null); + expect(data.length).to.be.equal(11078); + done(); + }); + }); + }); +});