How to map screen coordinates into local gui node texture pixel coordinate?

I have funtion screen_to_pixel, use case of this funtion - Handle Coordinate Mapping: Convert mouse/touch screen coordinates into local pixel coordinates x, y of the GUI node. This must account for the node’s screen position, scale, and pivot point (assuming Center pivot), window size to ensure the click aligns perfectly with the underlying pixel data.

My Issue - But as gui node is adjust to fit and when window is resize to different size than the project display size than mouse touch coordinate misalign with pixel coordinated. So i need solution for this.

-- Configuration: Use the same name as your GUI node
local CANVAS_NODE_NAME = "canvas"
local TEXTURE_NAME = "canvas_tx"

local function is_transparent(r, g, b, a)
	return a == 0
end

local function is_black_outline(r, g, b, a)
	return a > 0 and r < 20 and g < 20 and b < 20
end

local function is_white_fillable(r, g, b, a)
	return a > 0 and r > 240 and g > 240 and b > 240
end


-- Helper to get pixel color from stream
local function get_pixel(stream, x, y, width)
	local i = (y * width + x) * 4 + 1
	return stream[i], stream[i+1], stream[i+2], stream[i+3]
end

-- Flood Fill Algorithm (Queue-based)
local function flood_fill(self, sx, sy, fr, fg, fb)
	local w, h = self.width, self.height
	local stream = self.stream

	local function idx(x, y)
		return (y * w + x) * 4 + 1
	end

	local sr, sg, sb, sa = get_pixel(stream, sx, sy, w)

	-- Only allow fill if starting pixel is white
	if not is_white_fillable(sr, sg, sb, sa) then
		return
	end

	local queue_x = {}
	local queue_y = {}
	local qh, qt = 1, 1

	queue_x[1] = sx
	queue_y[1] = sy

	while qh <= qt do
		local x = queue_x[qh]
		local y = queue_y[qh]
		qh = qh + 1

		if x >= 0 and x < w and y >= 0 and y < h then
			local i = idx(x, y)

			local r = stream[i]
			local g = stream[i + 1]
			local b = stream[i + 2]
			local a = stream[i + 3]

			-- Only fill white pixels
			if is_white_fillable(r, g, b, a) then
				-- Paint
				stream[i]     = fr
				stream[i + 1] = fg
				stream[i + 2] = fb
				stream[i + 3] = 255

				qt = qt + 1; queue_x[qt] = x + 1; queue_y[qt] = y
				qt = qt + 1; queue_x[qt] = x - 1; queue_y[qt] = y
				qt = qt + 1; queue_x[qt] = x;     queue_y[qt] = y + 1
				qt = qt + 1; queue_x[qt] = x;     queue_y[qt] = y - 1
			end
		end
	end
end


function init(self)
	msg.post(".", "acquire_input_focus")
	
	self.canvas_node = gui.get_node(CANVAS_NODE_NAME)

	-- Load Image Resource
	local image_src = "/game/level_selector/images/outline.png"
	local data = sys.load_resource(image_src)
	local img = image.load(data)

	self.width = img.width
	self.height = img.height

	-- Setup editable buffer (RGBA)
	self.buffer = buffer.create(self.width * self.height, {
		{ name = hash("rgba"), type = buffer.VALUE_TYPE_UINT8, count = 4 }
	})
	self.stream = buffer.get_stream(self.buffer, hash("rgba"))

	-- Copy initial image into our stream
	local src_bytes = img.buffer
	for i = 1, #src_bytes do
		self.stream[i] = string.byte(src_bytes, i)
	end

	gui.new_texture("canvas_tx", self.width, self.height, "rgba", img.buffer)
	gui.set_texture(self.canvas_node, TEXTURE_NAME)
end

local function screen_to_pixel(self, action)
	local node = self.canvas_node

	local pos   = gui.get_position(node)
	local size  = gui.get_size(node)
	local scale = gui.get_scale(node)

	local w = size.x * scale.x
	local h = size.y * scale.y

	local left   = pos.x - w * 0.5
	local bottom = pos.y - h * 0.5

	local nx = (action.x - left) / w
	local ny = (action.y - bottom) / h

	if nx < 0 or nx > 1 or ny < 0 or ny > 1 then
		return nil
	end

	local px = math.floor(nx * self.width)
	local py = math.floor((1.0 - ny) * self.height)

	return px, py
end

function on_input(self, action_id, action)
	if action_id == hash("touch") and action.pressed then
		local px, py = screen_to_pixel(self, action)
		
		if not px then return end

		flood_fill(self, px, py, 255, 0, 0)

		gui.set_texture_data(
		TEXTURE_NAME,
		self.width,
		self.height,
		"rgba",
		buffer.get_bytes(self.buffer, true)
	)
end
end

Here in image you can see missalignment pointer was outside of red fill portion.

Use action.screen_x and action.screen_y for the mouse click data