Auto Draw Open Source
Key Required

Auto Draw Open Source

Draw and Guess!litensorlitensor 26.5K views 956 executesUpdated May 12, 2025
1.3/ 5(4 votes)
1325% Positive
Lua Script•395 lines (12235 chars)
-- This single-file Luau library implements read/write support for the
-- `Windows Bitmap`/`device-independent bitmap` file format version 3.0.

-- License: MIT

-- offsets into a Windows Bitmap file header
local bmp_header_offset = 0
local bmp_header_filesize = 2
local bmp_header_pixel_offset = 10
local bmp_header_size = 14
local bmp_header_width = 18
local bmp_header_height = 22
local bmp_header_planes = 26
local bmp_header_bpp = 28
local bmp_header_compression = 30
local bmp_header_image_size = 34
local bmp_header_bitmask_r = 54
local bmp_header_bitmask_g = 58
local bmp_header_bitmask_b = 62
local bmp_header_bitmask_a = 66

local function get_data_size(width: number, height: number, bpp: number): number
	local line_w = math.ceil(width/4)*4
	return height*line_w*(bpp/8)
end

local function new_bitmap()
	-- this table is returned as an interface for a Windows Bitmap to the user:
	local bmp = {}

	-- reading 8/16/32-bit little-endian integer values from a string
	function bmp:read(offset: number): number -- read uint8
		offset = math.floor(offset)
		local value = self.data[offset]:byte()
		return value
	end

	function bmp:read_word(offset: number): number -- read uint16
		return self:read(offset+1)*0x100 + self:read(offset)
	end

	function bmp:read_dword(offset: number): number -- read uint32
		return self:read(offset+3)*0x1000000 + self:read(offset+2)*0x10000 + self:read(offset+1)*0x100 + self:read(offset)
	end

	function bmp:read_long(offset: number): number -- read int32
		local value = self:read_dword(offset)
		if value >= 0x80000000 then -- bitmap format uses two's complement
			value = -(value - 0x80000000)
		end
		return value
	end

	-- writing 8/16/32-bit little-endian integer values from a string
	function bmp:write(offset: number, data: string) -- write uint8*
		offset = math.floor(offset)
		for i=1, #data do
			local index = offset+i-1
			local value = data:sub(i,i)
			self.data[index] = value -- only update, don't create new entry
		end
	end

	function bmp:write_word(offset: number, value: number) -- write uint16
		local a = math.floor(value % 0x100)
		local b = math.floor(value / 0x100)
		local data = string.char(a,b)
		self:write(offset, data)
	end

	function bmp:write_dword(offset: number, value: number) -- write uint32
		local a = math.floor(value) % 0x100
		local b = math.floor(value / 0x100) % 0x100
		local c = math.floor(value / 0x10000) % 0x100
		local d = math.floor(value / 0x1000000) % 0x100
		local data = string.char(a,b,c,d)
		self:write(offset, data)
	end

	function bmp:write_long(offset: number, value: number)
		if value < 0 then
			value = -value + 0x80000000
		end
		self:write_dword(offset, value)
	end

	-- read bitmap headers and parse required metadata, update self
	function bmp:read_header(): (boolean, string?)
		-- check the bitmap header
		if self:read_word(bmp_header_offset) ~= 0x4D42 then
			return false, "Bitmap magic header not found"
		end

		local compression = self:read_dword(bmp_header_compression)
		if compression == 3 then -- BI_BITFIELDS
			-- For 16bpp and 32bpp with bitfields compression
			-- We'll assume standard RGBA bitmasks if not specified
			self.bitmask_r = self:read_dword(bmp_header_bitmask_r)
			self.bitmask_g = self:read_dword(bmp_header_bitmask_g)
			self.bitmask_b = self:read_dword(bmp_header_bitmask_b)
			self.bitmask_a = self:read_dword(bmp_header_bitmask_a)
		elseif compression ~= 0 then -- BI_RGB
			return false, "Only uncompressed or BI_BITFIELDS bitmaps supported. Is: "..tostring(compression)
		end

		-- get bits per pixel from the bitmap header
		self.bpp = self:read_word(bmp_header_bpp)
		if not (self.bpp == 24 or self.bpp == 32 or (compression == 3 and (self.bpp == 16 or self.bpp == 32))) then
			return false, "Only 24bpp/32bpp bitmaps supported (or 16bpp with BI_BITFIELDS). Is: "..tostring(self.bpp)
		end

		-- get other required info from the bitmap header
		self.pixel_offset = self:read_dword(bmp_header_pixel_offset)
		self.width = self:read_long(bmp_header_width)
		self.height = self:read_long(bmp_header_height)

		-- calculate expected size of the data region
		self.data_size = get_data_size(self.width, self.height, self.bpp)

		-- if height is <0, the image data is in topdown format
		self.topdown = true
		if self.height < 0 then
			self.topdown = false
			self.height = -self.height
		end

		return true
	end

	-- write bitmap headers from self
	function bmp:write_header(width: number, height: number, bpp: number): (boolean, string?)
		if (width < 0) or (height < 0) or (width >= 2^31) or (height >= 2^31) then
			return false, "Invalid dimensions"
		end
		if not (bpp == 24 or bpp == 32) then
			return false, "Invalid bpp"
		end

		-- update expected data size
		self.data_size = get_data_size(width, height, bpp)

		-- Bitmap header
		self:write_word(bmp_header_offset, 0x4D42)

		self:write_dword(bmp_header_filesize, 54+self.data_size)
		self:write_dword(bmp_header_size, 40)
		self:write_word(bmp_header_planes, 1)

		-- image information
		self:write_dword(bmp_header_compression, 0) -- BI_RGB
		self:write_word(bmp_header_bpp, bpp)
		self:write_dword(bmp_header_pixel_offset, self.pixel_offset)

		self:write_long(bmp_header_width, width)
		self:write_dword(bmp_header_image_size, self.data_size)

		if self.topdown then
			self:write_long(bmp_header_height, height)
		else
			self:write_long(bmp_header_height, -height)
		end

		-- set all internal values accordingly
		return self:read_header()
	end

	-- return the r,g,b,a[0-255] color value for a pixel by its x,y coordinates
	function bmp:get_pixel(x: number, y: number): (number?, number?, number?, number?, string?)
		if (x < 0) or (x >= self.width) or (y < 0) or (y >= self.height) then
			return nil, nil, nil, nil, "Out of bounds"
		end

		-- calculate byte offset in data
		local Bpp = self.bpp/8
		local row_len = self.width * Bpp -- length of pixel data in bytes
		local row_stride = math.ceil(row_len/4)*4 -- padded length of a row in bytes
		local index = self.pixel_offset + y*row_stride + x*Bpp
		if self.topdown then
			index = self.pixel_offset + (self.height-y-1)*row_stride + x*Bpp
		end

		-- read r,g,b color values
		local b = self:read(index)
		local g = self:read(index+1)
		local r = self:read(index+2)

		local a = nil
		if Bpp == 4 then -- on 32bpp, also get 4th channel value(alpha, not in spec)
			a = self:read(index+3)
		end

		return r,g,b,a
	end

	-- set the color value at x,y
	function bmp:set_pixel(x: number, y: number, r: number, g: number, b: number, a: number?): (boolean, string?)
		if (x < 0) or (x >= self.width) or (y < 0) or (y >= self.height) then
			return false, "out of bounds"
		end
		if (r < 0) or (r > 255) or (g < 0) or (g > 255) or (b < 0) or (b > 255) then
			return false, "invalid color"
		end
		if a and ((a < 0) or (a > 255)) then
			return false, "invalid alpha"
		end

		-- calculate byte offset in data
		local Bpp = self.bpp/8
		local row_len = self.width * Bpp -- length of pixel data in bytes
		local row_stride = math.ceil(row_len/4)*4 -- padded length of a row in bytes
		local index = self.pixel_offset + y*row_stride + x*Bpp
		if self.topdown then
			index = self.pixel_offset + (self.height-y-1)*row_stride + x*Bpp
		end

		-- write new pixel value
		if Bpp == 3 then
			self:write(index, string.char(b,g,r))
		else
			self:write(index, string.char(b,g,r,a or 255))
		end

		return true
	end

	-- return the entire bitmap serialized
	function bmp:tostring(): string
		local result = {}
		for i = 0, #self.data do
			table.insert(result, self.data[i])
		end
		return table.concat(result)
	end

	return bmp
end

-- Create a new empty bitmap
local function new_empty_bitmap(width: number, height: number, alpha: boolean?): (any, string?)
	local bmp = new_bitmap()
	local bpp = 24
	if alpha then
		bpp = 32
	end

	-- create empty data string
	bmp.data = {}
	for i=0, 54+get_data_size(width, height, bpp) do
		bmp.data[i] = "\000"
	end
	bmp.pixel_offset = 54
	bmp.topdown = true

	-- write a new header to the data
	local ok, err = bmp:write_header(width, height, bpp)
	if not ok then
		return nil, err
	end

	return bmp
end

-- Read a bitmap from a string and return it
local function new_bitmap_from_string(data: string): (any, string?)
	local bmp = new_bitmap()

	-- copy data from string to internal table
	bmp.data = {}
	for i=1, #data do
		bmp.data[i-1] = data:sub(i,i)
	end

	-- read the header from the data
	local ok, err = bmp:read_header()
	if not ok then
		return nil, err
	end

	return bmp
end

-- Read a bitmap from a file and return it
local function new_bitmap_from_file(path: string): (any, string?)
	-- open a file containing bitmap data
	local file = io.open(path, "rb")
	if not file then
		return nil, "can't open input file for reading: "..tostring(path)
	end

	-- try to read from the file
	local data = file:read("*a")
	if (not data) or (data == "") then
		return nil, "can't read input file: "..tostring(path)
	end
	file:close()

	return new_bitmap_from_string(data)
end

-- this is the module returned to the user when require()'d
local Bitmap = {
	empty_bitmap = new_empty_bitmap,
	from_string = new_bitmap_from_string,
	from_file = new_bitmap_from_file,
	_new_bitmap = new_bitmap
}

local new_image = readfile("cock.bmp")
local bits, err = Bitmap.from_string(new_image)

local draw = require(game.ReplicatedStorage.modules.Draw)
local tools = require(game:GetService("Players").LocalPlayer.PlayerGui.ScreenGui.LocalScript.Modules.Tools)

local mouse = game.Players.LocalPlayer:GetMouse()
getgenv().actualLocation = Vector2.new(0,0)

if getgenv().__hooked == nil then
	local hook
	hook = hookmetamethod(game, "__index", newcclosure(function(self, a)
		if self == mouse and tostring(a) == "X" then
			return getgenv().actualLocation.X
		end
		if self == mouse and tostring(a) == "Y" then
			return getgenv().actualLocation.Y
		end
		return hook(self, a)
	end))

	getgenv().__hooked = true;
end

draw.drawInfo.BrushSize = 8

local function colorDifference(r1, g1, b1, r2, g2, b2)
	return math.sqrt((r2 - r1)^2 + (g2 - g1)^2 + (b2 - b1)^2)
end

local threshold = 8 -- Color difference threshold

for y = 0, bits.height - 1, 16 do
	local startX = nil
	local startColor = nil

	draw.drawInfo.LastMousePosition = nil
	
	for x = 0, bits.width - 1, 16 do
		local r, g, b, a = bits:get_pixel(x, y)

		if (a ~= nil and a > 0) or (a == nil) then
			if not startX then
				startX = x
				startColor = {r, g, b}
			else
				local diff = colorDifference(r, g, b, unpack(startColor))
				if diff > threshold then
					draw.drawInfo.LastMousePosition = nil
					-- Draw line from startX to (x - 16)
					draw.drawInfo.Color = Color3.fromRGB(unpack(startColor))
					getgenv().actualLocation = Vector2.new(1920 / 2 + startX / 4, 1080 / 4 + y / 4)
					draw:attemptDraw(game:GetService("Players").LocalPlayer.PlayerGui.ScreenGui.Game.Canvas, true)
					getgenv().actualLocation = Vector2.new(1920 / 2 + (x - 16) / 4, 1080 / 4 + y / 4)
					draw:attemptDraw(game:GetService("Players").LocalPlayer.PlayerGui.ScreenGui.Game.Canvas, true)
					startX = x
					startColor = {r, g, b}
				end
			end
		else
			if startX then
				-- Draw line from startX to (x - 16)
				draw.drawInfo.LastMousePosition = nil
				
				draw.drawInfo.Color = Color3.fromRGB(unpack(startColor))
				getgenv().actualLocation = Vector2.new(1920 / 2 + startX / 4, 1080 / 4 + y / 4)
				draw:attemptDraw(game:GetService("Players").LocalPlayer.PlayerGui.ScreenGui.Game.Canvas, true)
				getgenv().actualLocation = Vector2.new(1920 / 2 + (x - 16) / 4, 1080 / 4 + y / 4)
				draw:attemptDraw(game:GetService("Players").LocalPlayer.PlayerGui.ScreenGui.Game.Canvas, true)
				startX = nil
				startColor = nil
			end
		end
	end

	-- Draw remaining line at the end of the row
	if startX then
		draw.drawInfo.LastMousePosition = nil
		
		draw.drawInfo.Color = Color3.fromRGB(unpack(startColor))
		getgenv().actualLocation = Vector2.new(1920 / 2 + startX / 4, 1080 / 4 + y / 4)
		draw:attemptDraw(game:GetService("Players").LocalPlayer.PlayerGui.ScreenGui.Game.Canvas, true)
		getgenv().actualLocation = Vector2.new(1920 / 2 + (bits.width - 1) / 4, 1080 / 4 + y / 4)
		draw:attemptDraw(game:GetService("Players").LocalPlayer.PlayerGui.ScreenGui.Game.Canvas, true)
	end

	task.wait()
end

Features & Information

open source goofy auto draw made in 5 minutes how to use: - get any png/jpg - go to a free converter site and convert it to a BMP with size 1024x1024 - put the downloaded BMP into your workspace folder and give it a simple name - in the script (at readfile("abc.bmp")) change the abc to the file name. - go in game, when its your turn to draw, execute the script and focus on the roblox window - draw use execturo with hook support (wave, swift, etc), not solara. (but you can port it to solara by manually writing the mouse position)
#auto draw#draw#guess#script#open source

Community Comments (1)

TAISOBITETAISOBITEMay 12, 2025

ITS NEVER LUPUS