diff --git a/README.md b/README.md index d6f6eb4..8a24ef1 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,40 @@ -![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 - -## Learning Objectives -* students will be able to manipulate binary data using the node.js `Buffer` class -* students will be able to architect modular solutions to solving problems - -## Resources -* [Bitmap Specification](https://en.wikipedia.org/wiki/BMP_file_format) -* [NodeJS Buffer docs](https://nodejs.org/api/buffer.html) - -#### Feature Tasks - -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 `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 - -#### Suggested Directory Structure (this is optional): -* suggested directory structure: - - **index.js** - - **lib** - - bitmap file helper - - **model** - - bitmap constructor - - color constructor - - **test** - - bitmap file helper test - - bitmap constructor test - - color constructor test - -#### Rubric: -* **tests:** 3pts -* **package.json:** 2pts -* **read bitmap meta data:** 5pts -* **successfully apply transforms:** 5pts -* **project design and organization:** 5pts +-----Bitmap Transformer----- + +Overview: Bitmap transformer reads a bitmap file and applies one of ten transformations to the file. The modified file is saved as a separate entity. +The project is separated into two major components and a test. Additionally, there is a directory of assets which contains the original bitmap image as well as the modified duplicates. + + The directory branch is as follows: + + bitmapper_ram-rod + | + |--assets + | + |--lib + | + |--model + | + |--node_modules + | + |--test + + -----Directory Overview----- + +---assets--- + +*Houses all bitmap originals and modified duplicates* + +---model--- + +bitmap.js module: Exports the bitmapper object which models bitmaps as javaScript objects. The bitmapper objects properties are described below: + +Bitmap : serves as an object constructor. All of bitmaps properties correspond to meta data that is interpreted from the bitmap image as buffer housing hexi-decimal values using node's native fs module. properties also hold information that indicates the start and end points of various sections of the file. The original buffer is stored as a property of the instantiated object. + +Bitmap.newFile : method of the Bitmap constructor that takes a string as a parameter. The string will serve as the file name for a new image. The stored buffer in the object will be used to write the new file. + +renderImage : Wrapper for node's native fs module. Takes a path, a callback and a file name as parameters. The path will be used with fs.readFile to retrieve a buffer from a bitmap file. The buffer will be instantiated as a new Bitmap object The callback will come from the transformer.js module. This dictates what type of transformation to apply to the buffer. Finally, the name will be passed to the instantiated Bitmap's newFile method. + +---lib--- + +transformer.js module: exports the transform object which applies 1 of 10 different transformations to a Bitmap object that is passed as a parameter. The transform object properties are described below: + +modify : Serves as the primary function in transformer.js. All other methods will serve as a wrapper for modify. Modify requires 4 parameters; a Bitmap object, and 3 callbacks. Each callback will be applied to one of the 3 portions of a hex color; red, green and blue. The Bitmap object is iterated over using the stored integers that represent the start and end position of the color palette. The iterations increment by 4. One for red, green, blue and the padding. The Bitmap's stored buffer uses it's readUInt8 method on i - 3, i - 2, and i - 1. to return a value between 0 and 255. Those values are then passed to the callbacks. The return of the call back is then written back into the buffer at it's relative position using Buffer.writeUint8. diff --git a/bitmap-ram-rod/.eslintignore b/bitmap-ram-rod/.eslintignore new file mode 100644 index 0000000..05b1cf3 --- /dev/null +++ b/bitmap-ram-rod/.eslintignore @@ -0,0 +1,5 @@ +**/node_modules/* +**/vendor/* +**/*.min.js +**/coverage/* +**/build/* diff --git a/bitmap-ram-rod/.eslintrc b/bitmap-ram-rod/.eslintrc new file mode 100644 index 0000000..8dc6807 --- /dev/null +++ b/bitmap-ram-rod/.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/bitmap-ram-rod/.gitignore b/bitmap-ram-rod/.gitignore new file mode 100644 index 0000000..345130c --- /dev/null +++ b/bitmap-ram-rod/.gitignore @@ -0,0 +1,136 @@ +# Created by https://www.gitignore.io/api/osx,vim,node,macos,windows + +### macOS ### +*.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 + +### 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 ### + +# Icon must end with two \r + +# Thumbnails + +# Files that might appear in the root of a volume + +# Directories potentially created on remote AFP share + +### Vim ### +# swap +[._]*.s[a-v][a-z] +[._]*.sw[a-p] +[._]s[a-v][a-z] +[._]sw[a-p] +# session +Session.vim +# temporary +.netrwhist +*~ +# auto-generated tag files +tags + +### 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,vim,node,macos,windows diff --git a/bitmap-ram-rod/assets/palette-bitmap-blackOut.bmp b/bitmap-ram-rod/assets/palette-bitmap-blackOut.bmp new file mode 100644 index 0000000..ddc1f9a Binary files /dev/null and b/bitmap-ram-rod/assets/palette-bitmap-blackOut.bmp differ diff --git a/bitmap-ram-rod/assets/palette-bitmap-blueShift.bmp b/bitmap-ram-rod/assets/palette-bitmap-blueShift.bmp new file mode 100644 index 0000000..1eb57e2 Binary files /dev/null and b/bitmap-ram-rod/assets/palette-bitmap-blueShift.bmp differ diff --git a/bitmap-ram-rod/assets/palette-bitmap-greenShift.bmp b/bitmap-ram-rod/assets/palette-bitmap-greenShift.bmp new file mode 100644 index 0000000..a809f3c Binary files /dev/null and b/bitmap-ram-rod/assets/palette-bitmap-greenShift.bmp differ diff --git a/bitmap-ram-rod/assets/palette-bitmap-greyScale.bmp b/bitmap-ram-rod/assets/palette-bitmap-greyScale.bmp new file mode 100644 index 0000000..bd1b35e Binary files /dev/null and b/bitmap-ram-rod/assets/palette-bitmap-greyScale.bmp differ diff --git a/bitmap-ram-rod/assets/palette-bitmap-invert.bmp b/bitmap-ram-rod/assets/palette-bitmap-invert.bmp new file mode 100644 index 0000000..d835124 Binary files /dev/null and b/bitmap-ram-rod/assets/palette-bitmap-invert.bmp differ diff --git a/bitmap-ram-rod/assets/palette-bitmap-noBlue.bmp b/bitmap-ram-rod/assets/palette-bitmap-noBlue.bmp new file mode 100644 index 0000000..49ba9c2 Binary files /dev/null and b/bitmap-ram-rod/assets/palette-bitmap-noBlue.bmp differ diff --git a/bitmap-ram-rod/assets/palette-bitmap-noGreen.bmp b/bitmap-ram-rod/assets/palette-bitmap-noGreen.bmp new file mode 100644 index 0000000..4e3ce8b Binary files /dev/null and b/bitmap-ram-rod/assets/palette-bitmap-noGreen.bmp differ diff --git a/bitmap-ram-rod/assets/palette-bitmap-noRed.bmp b/bitmap-ram-rod/assets/palette-bitmap-noRed.bmp new file mode 100644 index 0000000..53ded55 Binary files /dev/null and b/bitmap-ram-rod/assets/palette-bitmap-noRed.bmp differ diff --git a/bitmap-ram-rod/assets/palette-bitmap-redShift.bmp b/bitmap-ram-rod/assets/palette-bitmap-redShift.bmp new file mode 100644 index 0000000..0377666 Binary files /dev/null and b/bitmap-ram-rod/assets/palette-bitmap-redShift.bmp differ diff --git a/bitmap-ram-rod/assets/palette-bitmap-whiteOut.bmp b/bitmap-ram-rod/assets/palette-bitmap-whiteOut.bmp new file mode 100644 index 0000000..47c94fe Binary files /dev/null and b/bitmap-ram-rod/assets/palette-bitmap-whiteOut.bmp differ diff --git a/assets/palette-bitmap.bmp b/bitmap-ram-rod/assets/palette-bitmap.bmp similarity index 100% rename from assets/palette-bitmap.bmp rename to bitmap-ram-rod/assets/palette-bitmap.bmp diff --git a/bitmap-ram-rod/index.js b/bitmap-ram-rod/index.js new file mode 100644 index 0000000..03792e0 --- /dev/null +++ b/bitmap-ram-rod/index.js @@ -0,0 +1,13 @@ +'use strict'; + +const bitmapper = require('./model/bitmap.js'); +const transform = require('./lib/transformer.js'); + +let transformations = Object.keys(transform); +let fileName = 'palette-bitmap'; + +console.log(bitmapper); + +for(let i = 1; i < transformations.length; i++) { + bitmapper(`./assets/${fileName}.bmp`, transform[transformations[i]], `${fileName}-${transformations[i]}`); +} diff --git a/bitmap-ram-rod/lib/transformer.js b/bitmap-ram-rod/lib/transformer.js new file mode 100644 index 0000000..bce4d86 --- /dev/null +++ b/bitmap-ram-rod/lib/transformer.js @@ -0,0 +1,100 @@ +'use strict'; + +const transform = module.exports = {}; + +transform.modify = (parent, blueCallback, greenCallback, redCallback) => { + + const helper = (bmp, position) => (callback) => (...ind) => { + let hexColors = [bmp.readUInt8(ind[0]), bmp.readUInt8(ind[1]), bmp.readUInt8(ind[2])]; + + return bmp.writeUInt8(callback(...hexColors), position); + }; + let start = parent.colorTableStartPoint, end = parent.colorTableEndPoint; + + for (let i = 3 + start; i < end; i+=4) { + helper(parent.buffer, i - 3)(blueCallback)(i-3, i-2, i-1); + helper(parent.buffer, i - 2)(greenCallback)(i-2, i-3, i-1); + helper(parent.buffer, i - 1)(redCallback)(i-1, i-2, i-3); + } +}; + +transform.blueShift = function(buffer) { + transform.modify(buffer, + hexBlue => hexBlue, + () => 0, + () => 0 + ); +}; + +transform.redShift = function(buffer) { + transform.modify(buffer, + () => 0, + () => 0, + hexRed => hexRed + ); +}; + +transform.greenShift = function(buffer) { + transform.modify(buffer, + () => 0, + hexGreen => hexGreen, + () => 0 + ); +}; + +transform.noGreen = function(buffer) { + transform.modify(buffer, + (blue) => blue, + () => 0, + (red) => red + ); +}; + +transform.noBlue = function(buffer) { + transform.modify(buffer, + () => 0, + (green) => green, + (red) => red + ); +}; + +transform.noRed = function(buffer) { + transform.modify(buffer, + (blue) => blue, + (green) => green, + () => 0 + ); +}; + +transform.blackOut = function(buffer) { + transform.modify(buffer, + () => 0, + () => 0, + () => 0 + ); +}; + +transform.whiteOut = function(buffer) { + transform.modify(buffer, + () => 255, + () => 255, + () => 255 + ); +}; + +transform.greyScale = function(buffer) { + transform.modify(buffer, + (first, second, third) => (first + second + third) / 3, + (first, second) => second, + (first, second, third) => third + ); +}; + +transform.invert = function(buffer) { + let helper = (val) => 255 - val; + transform.modify(buffer, + helper, + helper, + helper + ); +}; diff --git a/bitmap-ram-rod/model/bitmap.js b/bitmap-ram-rod/model/bitmap.js new file mode 100644 index 0000000..64040b8 --- /dev/null +++ b/bitmap-ram-rod/model/bitmap.js @@ -0,0 +1,45 @@ +'use srict'; + +const fs = require('fs'); + +const bitmapper = module.exports = {}; + + +function Bitmap(buffer) { + this.type = buffer.toString('utf-8', 0, 2); + this.pixelTableStart = buffer.readUInt16LE(10); + this.bitsPerPixel = buffer.readUInt16LE(28); + this.headerSize = buffer.readUInt16LE(14); + + this.bitsPerPixel < 16 ? + this.colorTableStartPoint = this.headerSize + 14 : + this.pixelTableStart = this.headerSize + 14 ; + + this.pixelTableEnd = buffer.length; + + this.colorTableStartPoint ? + this.colorTableEndPoint = this.pixelTableStart: + null; + this.buffer = buffer; +} + +Bitmap.prototype.newFile = function(fileName) { + let newBuffer = this.buffer; + fs.writeFile(`./assets/${fileName}.bmp`, newBuffer, (err) => { + if(err) console.error(err); + console.log(`${fileName}.bmp created!!`); + }); +}; + +bitmapper.Bitmap = Bitmap; + + + +bitmapper.renderImage = function(path, callback, newFileName) { + fs.readFile(path, (err, asset) => { + if(err) console.error(err); + let bitmap = new bitmapper.Bitmap(asset); + callback(bitmap); + bitmap.newFile(newFileName); + }); +}; diff --git a/bitmap-ram-rod/package.json b/bitmap-ram-rod/package.json new file mode 100644 index 0000000..8841ab5 --- /dev/null +++ b/bitmap-ram-rod/package.json @@ -0,0 +1,19 @@ +{ + "name": "bitmap-ram-rod", + "version": "1.0.0", + "description": "", + "main": "index.js", + "directories": { + "test": "test" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "chai": "^4.1.0", + "mocha": "^3.4.2" + } +} diff --git a/bitmap-ram-rod/test/bitmap-test.js b/bitmap-ram-rod/test/bitmap-test.js new file mode 100644 index 0000000..b65fc2a --- /dev/null +++ b/bitmap-ram-rod/test/bitmap-test.js @@ -0,0 +1,81 @@ +'use strict' ; + +const fs = require('fs'); +const expect = require('chai').expect; +const bitmapper = require(`${__dirname}/../model/bitmap.js`); + +let path = './assets/palette-bitmap.bmp'; + +describe('Test for Bitmap object Constructor', () => { + describe('Object construction', () => { + it('Should have throw an err', (done) => { + fs.readFile('./assets/not-an-image.bmp', (err) => { + if(err) console.error(err); + expect(err).to.be.an('error'); + done(); + }); + }); + it('Should create a new Bitmap object', (done) => { + fs.readFile(path, (err, asset) => { + if(err) return console.error(err); + let testBitmap = new bitmapper.Bitmap(asset); + expect(testBitmap).to.be.an('object'); + done(); + }); + }); + it('Should have the same file type as the asset', (done) => { + fs.readFile(path, (err, asset) => { + if(err) return console.error(err); + let testBitmap = new bitmapper.Bitmap(asset); + expect(testBitmap).to.have.property('type', 'BM'); + expect(testBitmap.type).to.equal(asset.toString('utf-8', 0, 2)); + done(); + }); + }); + it('Should have the same the same pixel table offset as the asset', (done) => { + fs.readFile(path, (err, asset) => { + if(err) return console.error(err); + let testBitmap = new bitmapper.Bitmap(asset); + expect(testBitmap).to.have.property('pixelTableStart', 1078); + expect(testBitmap.pixelTableStart).to.equal(asset.readUInt16LE(10)); + done(); + }); + }); + it('Should have the same bits per pixel as the asset', (done) => { + fs.readFile(path, (err, asset) => { + if(err) return console.error(err); + let testBitmap = new bitmapper.Bitmap(asset); + expect(testBitmap).to.have.property('bitsPerPixel', 8); + expect(testBitmap.bitsPerPixel).to.equal(asset.readUInt16LE(28)); + done(); + }); + }); + it('Should have the same header size as the asset', (done) => { + fs.readFile(path, (err, asset) => { + if(err) return console.error(err); + let testBitmap = new bitmapper.Bitmap(asset); + expect(testBitmap).to.have.property('headerSize', 40); + expect(testBitmap.headerSize).to.equal(asset.readUInt16LE(14)); + done(); + }); + }); + it('pixelTableEnd should equal the length of the buffer', (done) => { + fs.readFile(path, (err, asset) => { + if(err) return console.error(err); + let testBitmap = new bitmapper.Bitmap(asset); + expect(testBitmap).to.have.property('pixelTableEnd', 11078); + expect(testBitmap.pixelTableEnd).to.equal(asset.length); + done(); + }); + }); + it('It\'s color table should end at the stored offset point', (done) => { + fs.readFile(path, (err, asset) => { + if(err) return console.error(err); + let testBitmap = new bitmapper.Bitmap(asset); + expect(testBitmap).to.have.property('colorTableEndPoint', 1078); + expect(testBitmap.colorTableEndPoint).to.equal(asset.readUInt16LE(10)); + done(); + }); + }); + }); +}); diff --git a/bitmap-ram-rod/test/transformer-test.js b/bitmap-ram-rod/test/transformer-test.js new file mode 100644 index 0000000..c54f658 --- /dev/null +++ b/bitmap-ram-rod/test/transformer-test.js @@ -0,0 +1,50 @@ +'use strict'; + +const expect = require('chai').expect; +const fs = require('fs'); +const transform = require('./../lib/transformer.js'); +const bitmapper = require('./../model/bitmap.js'); + +let path = './assets/palette-bitmap.bmp'; + +describe('Transformer Module Tests', () => { + describe('#modify', () => { + it('The file header should remain the same', (done) => { + fs.readFile(path, (err, asset) => { + let testBitmap = new bitmapper.Bitmap(asset); + let header = asset.toString('hex', 0, 14); + transform.modify(testBitmap, () => 0, () => 0, () => 0); + expect(asset.toString('hex', 0, 14)).to.equal(header); + done(); + }); + + }); + it('The DIB header should remain the same', (done) => { + fs.readFile(path, (err, asset) => { + let testBitmap = new bitmapper.Bitmap(asset); + let DIB = asset.toString('hex', 14, 54); + transform.modify(testBitmap, () => 0, () => 0, () => 0); + expect(asset.toString('hex', 14, 54)).to.equal(DIB); + done(); + }); + }); + it('The color palette should be modified', (done) => { + fs.readFile(path, (err, asset) => { + let testBitmap = new bitmapper.Bitmap(asset); + let colorPallete = asset.toString('hex', 54, 1078); + transform.modify(testBitmap, () => 0, () => 0, () => 0); + expect(asset.toString('hex', 54, 1078)).not.to.equal(colorPallete); + done(); + }); + }); + it('The pixel array should remain the same', (done) => { + fs.readFile(path, (err, asset) => { + let testBitmap = new bitmapper.Bitmap(asset); + let pixelArray = asset.toString('hex', 1078, asset.length); + transform.modify(testBitmap, () => 0, () => 0, () => 0); + expect(asset.toString('hex', 1078, asset.length)).to.equal(pixelArray); + done(); + }); + }); + }); +});